aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-db
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-07-04 00:34:24 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-07-04 17:00:08 +0200
commit1df148803610cd54f182b8636f01c0e6ece92b19 (patch)
tree8b6d2919ebe3575556b8796fd95a2b89996933ff /sonar-db
parent1018747567d50056a49aa7c8421d596f18f25344 (diff)
downloadsonarqube-1df148803610cd54f182b8636f01c0e6ece92b19.tar.gz
sonarqube-1df148803610cd54f182b8636f01c0e6ece92b19.zip
Extract module sonar-db
Diffstat (limited to 'sonar-db')
-rw-r--r--sonar-db/pom.xml135
-rw-r--r--sonar-db/src/main/java/org/sonar/batch/index/ResourceCopy.java29
-rw-r--r--sonar-db/src/main/java/org/sonar/core/issue/ActionPlanStats.java76
-rw-r--r--sonar-db/src/main/java/org/sonar/core/issue/db/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/core/issue/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/core/permission/ComponentPermissions.java38
-rw-r--r--sonar-db/src/main/java/org/sonar/core/permission/GlobalPermissions.java48
-rw-r--r--sonar-db/src/main/java/org/sonar/core/permission/GroupWithPermission.java83
-rw-r--r--sonar-db/src/main/java/org/sonar/core/permission/UserWithPermission.java79
-rw-r--r--sonar-db/src/main/java/org/sonar/core/permission/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/core/timemachine/Periods.java195
-rw-r--r--sonar-db/src/main/java/org/sonar/core/timemachine/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/core/user/DefaultUserFinder.java69
-rw-r--r--sonar-db/src/main/java/org/sonar/core/user/DeprecatedUserFinder.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/core/user/GroupMembership.java92
-rw-r--r--sonar-db/src/main/java/org/sonar/core/user/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/BatchSession.java221
-rw-r--r--sonar-db/src/main/java/org/sonar/db/Dao.java23
-rw-r--r--sonar-db/src/main/java/org/sonar/db/DaoUtils.java149
-rw-r--r--sonar-db/src/main/java/org/sonar/db/Database.java39
-rw-r--r--sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java69
-rw-r--r--sonar-db/src/main/java/org/sonar/db/DbSession.java201
-rw-r--r--sonar-db/src/main/java/org/sonar/db/DdlUtils.java68
-rw-r--r--sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java188
-rw-r--r--sonar-db/src/main/java/org/sonar/db/Dto.java49
-rw-r--r--sonar-db/src/main/java/org/sonar/db/IsAliveMapper.java29
-rw-r--r--sonar-db/src/main/java/org/sonar/db/MyBatis.java326
-rw-r--r--sonar-db/src/main/java/org/sonar/db/activity/ActivityDto.java104
-rw-r--r--sonar-db/src/main/java/org/sonar/db/activity/ActivityMapper.java26
-rw-r--r--sonar-db/src/main/java/org/sonar/db/activity/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentDto.java304
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentIndexMapper.java29
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentLinkDto.java90
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentLinkMapper.java35
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java110
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/FilePathWithHashDto.java61
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceDao.java373
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceDto.java226
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceIndexDto.java93
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerDao.java216
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerMapper.java31
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerQuery.java70
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterDao.java156
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterMapper.java39
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceMapper.java81
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/ResourceQuery.java63
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/SnapshotDto.java345
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java48
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/SnapshotQuery.java136
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/UuidWithProjectUuidDto.java45
-rw-r--r--sonar-db/src/main/java/org/sonar/db/component/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportDto.java143
-rw-r--r--sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportMapper.java49
-rw-r--r--sonar-db/src/main/java/org/sonar/db/compute/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDao.java84
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDto.java106
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardMapper.java37
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDao.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDto.java115
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/DashboardMapper.java50
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/WidgetDto.java205
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/WidgetMapper.java57
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyDto.java75
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyMapper.java46
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dashboard/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDao.java221
-rw-r--r--sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDto.java138
-rw-r--r--sonar-db/src/main/java/org/sonar/db/debt/CharacteristicMapper.java53
-rw-r--r--sonar-db/src/main/java/org/sonar/db/debt/RequirementMigrationDto.java159
-rw-r--r--sonar-db/src/main/java/org/sonar/db/debt/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/deprecated/ClusterAction.java28
-rw-r--r--sonar-db/src/main/java/org/sonar/db/deprecated/NullQueue.java30
-rw-r--r--sonar-db/src/main/java/org/sonar/db/deprecated/WorkQueue.java29
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java90
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java92
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/DialectUtils.java74
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/H2.java45
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/MsSql.java42
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/MySql.java54
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/Oracle.java44
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/PostgreSql.java52
-rw-r--r--sonar-db/src/main/java/org/sonar/db/dialect/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java64
-rw-r--r--sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java34
-rw-r--r--sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java115
-rw-r--r--sonar-db/src/main/java/org/sonar/db/duplication/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/event/EventDto.java132
-rw-r--r--sonar-db/src/main/java/org/sonar/db/event/EventMapper.java41
-rw-r--r--sonar-db/src/main/java/org/sonar/db/event/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDao.java118
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDto.java201
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/ActionPlanMapper.java44
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDao.java44
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDto.java187
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsMapper.java35
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDao.java135
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDto.java190
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueChangeMapper.java45
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueDao.java50
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueDto.java686
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDao.java117
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDto.java116
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDao.java86
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDto.java70
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteMapper.java41
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueFilterMapper.java47
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/IssueMapper.java42
-rw-r--r--sonar-db/src/main/java/org/sonar/db/issue/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDao.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java89
-rw-r--r--sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateMapper.java31
-rw-r--r--sonar-db/src/main/java/org/sonar/db/loadedtemplate/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureDto.java114
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureMapper.java47
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/MeasureDto.java268
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDao.java52
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDto.java109
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterMapper.java29
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java44
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/PastMeasureDto.java97
-rw-r--r--sonar-db/src/main/java/org/sonar/db/measure/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/metric/MetricDto.java199
-rw-r--r--sonar-db/src/main/java/org/sonar/db/metric/MetricMapper.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/db/metric/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDao.java83
-rw-r--r--sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDto.java97
-rw-r--r--sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueMapper.java38
-rw-r--r--sonar-db/src/main/java/org/sonar/db/notification/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/GroupWithPermissionDto.java68
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java91
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionFacade.java248
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java32
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionQuery.java199
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java275
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDto.java129
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateGroupDto.java98
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java61
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateUserDto.java107
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/UserWithPermissionDto.java67
-rw-r--r--sonar-db/src/main/java/org/sonar/db/permission/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/profiling/InvocationUtils.java46
-rw-r--r--sonar-db/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java407
-rw-r--r--sonar-db/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java56
-rw-r--r--sonar-db/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java52
-rw-r--r--sonar-db/src/main/java/org/sonar/db/profiling/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java302
-rw-r--r--sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/property/PropertyDto.java106
-rw-r--r--sonar-db/src/main/java/org/sonar/db/property/PropertyQuery.java76
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/IdUuidPair.java50
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java53
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java224
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeConfiguration.java81
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeDao.java244
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeListener.java33
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java95
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeProfiler.java104
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeSnapshotQuery.java130
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/PurgeableSnapshotDto.java94
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/DefaultPeriodCleaner.java69
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/DeleteAllFilter.java52
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/Filter.java30
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/Filters.java59
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/Interval.java73
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/KeepOneFilter.java88
-rw-r--r--sonar-db/src/main/java/org/sonar/db/purge/period/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociation.java79
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java58
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java68
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java31
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationQuery.java168
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java124
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java208
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java37
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java122
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDto.java72
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java37
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java67
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java159
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java107
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java68
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleParamDto.java104
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/ProjectQprofileAssociationDto.java51
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java376
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDto.java128
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java87
-rw-r--r--sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileProjectCount.java42
-rw-r--r--sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java67
-rw-r--r--sonar-db/src/main/java/org/sonar/db/rule/RuleDto.java403
-rw-r--r--sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/rule/RuleParamDto.java102
-rw-r--r--sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDao.java150
-rw-r--r--sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDto.java86
-rw-r--r--sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreMapper.java35
-rw-r--r--sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreUpdater.java80
-rw-r--r--sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java54
-rw-r--r--sonar-db/src/main/java/org/sonar/db/semaphore/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/source/FileSourceDto.java277
-rw-r--r--sonar-db/src/main/java/org/sonar/db/source/FileSourceMapper.java39
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/AuthorDao.java122
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/AuthorDto.java80
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/AuthorMapper.java38
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/AuthorizationDao.java134
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/AuthorizationMapper.java37
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupDto.java65
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupMapper.java46
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDao.java118
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDto.java81
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupMembershipMapper.java42
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupMembershipQuery.java164
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupRoleDto.java72
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/GroupUserCount.java34
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/LoginGroup.java34
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/RoleDao.java79
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/RoleMapper.java59
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserDao.java175
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserDto.java175
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserGroupDto.java45
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserGroupMapper.java30
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserMapper.java79
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserMembershipDto.java69
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserMembershipQuery.java164
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserRoleDto.java69
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java133
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationDto.java32
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationMapper.java28
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v44/ChangeLog.java118
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v44/Migration44Mapper.java63
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v44/ProfileMeasure.java50
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v44/QProfileDto44.java60
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v44/package-info.java25
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v45/Migration45Mapper.java63
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v45/Rule.java111
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v45/RuleParameter.java113
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v45/package-info.java24
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/Component.java121
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/Migration50Mapper.java128
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v50/package-info.java25
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/IsAliveMapper.xml16
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/activity/ActivityMapper.xml15
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ComponentIndexMapper.xml16
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ComponentLinkMapper.xml39
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml315
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ResourceIndexerMapper.xml69
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ResourceKeyUpdaterMapper.xml39
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/ResourceMapper.xml255
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml140
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/compute/AnalysisReportMapper.xml82
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/dashboard/ActiveDashboardMapper.xml58
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/dashboard/DashboardMapper.xml6
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetMapper.xml38
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetPropertyMapper.xml12
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/debt/CharacteristicMapper.xml137
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml25
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml80
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanMapper.xml88
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanStatsMapper.xml35
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/issue/IssueChangeMapper.xml71
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterFavouriteMapper.xml41
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterMapper.xml76
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/issue/IssueMapper.xml234
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/loadedtemplate/LoadedTemplateMapper.xml26
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/measure/CustomMeasureMapper.xml83
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/measure/MeasureFilterMapper.xml17
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml113
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/metric/MetricMapper.xml164
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/notification/NotificationQueueMapper.xml44
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml69
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml186
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml142
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml341
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml35
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml53
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml48
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml208
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml209
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml219
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/semaphore/SemaphoreMapper.xml36
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/source/FileSourceMapper.xml47
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/source/SnapshotDataMapper.xml39
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/AuthorMapper.xml33
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/AuthorizationMapper.xml210
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/GroupMapper.xml77
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/GroupMembershipMapper.xml92
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/RoleMapper.xml118
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/UserGroupMapper.xml26
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml141
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/SchemaMigrationMapper.xml14
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql348
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl686
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/v44/Migration44Mapper.xml105
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/v45/Migration45Mapper.xml7
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/v50/Migration50Mapper.xml7
-rw-r--r--sonar-db/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java43
-rw-r--r--sonar-db/src/test/java/org/sonar/core/timemachine/PeriodsTest.java262
-rw-r--r--sonar-db/src/test/java/org/sonar/core/user/DefaultUserFinderTest.java66
-rw-r--r--sonar-db/src/test/java/org/sonar/core/user/DefaultUserTest.java52
-rw-r--r--sonar-db/src/test/java/org/sonar/core/user/DeprecatedUserFinderTest.java86
-rw-r--r--sonar-db/src/test/java/org/sonar/db/AbstractDaoTestCase.java308
-rw-r--r--sonar-db/src/test/java/org/sonar/db/BatchSessionTest.java86
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DaoUtilsTest.java85
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DatabaseCommands.java211
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DatabaseUtilsTest.java145
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DbTester.java453
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DdlUtilsTest.java63
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java126
-rw-r--r--sonar-db/src/test/java/org/sonar/db/H2Database.java115
-rw-r--r--sonar-db/src/test/java/org/sonar/db/H2DatabaseTest.java51
-rw-r--r--sonar-db/src/test/java/org/sonar/db/IsAliveMapperTest.java55
-rw-r--r--sonar-db/src/test/java/org/sonar/db/MyBatisTest.java74
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ComponentDtoTest.java90
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ComponentLinkDtoTest.java49
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ResourceDaoTest.java470
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ResourceIndexerDaoTest.java153
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/ResourceKeyUpdaterDaoTest.java110
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/SnapshotDtoTest.java107
-rw-r--r--sonar-db/src/test/java/org/sonar/db/component/SnapshotQueryTest.java51
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dashboard/ActiveDashboardDaoTest.java115
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dashboard/DashboardDaoTest.java130
-rw-r--r--sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDaoTest.java244
-rw-r--r--sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDtoTest.java84
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java45
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dialect/H2Test.java59
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dialect/MsSqlTest.java57
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dialect/MySqlTest.java63
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dialect/OracleTest.java60
-rw-r--r--sonar-db/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java64
-rw-r--r--sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java68
-rw-r--r--sonar-db/src/test/java/org/sonar/db/event/EventDtoTest.java52
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/ActionPlanDaoTest.java122
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/ActionPlanStatsDaoTest.java52
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDaoTest.java197
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDtoTest.java141
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueChangeMapperTest.java76
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueDaoTest.java94
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueDtoTest.java150
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueFilterDaoTest.java129
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueFilterFavouriteDaoTest.java99
-rw-r--r--sonar-db/src/test/java/org/sonar/db/issue/IssueMapperTest.java191
-rw-r--r--sonar-db/src/test/java/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest.java55
-rw-r--r--sonar-db/src/test/java/org/sonar/db/measure/MeasureDtoTest.java75
-rw-r--r--sonar-db/src/test/java/org/sonar/db/measure/MeasureFilterDaoTest.java68
-rw-r--r--sonar-db/src/test/java/org/sonar/db/measure/PastMeasureDtoTest.java72
-rw-r--r--sonar-db/src/test/java/org/sonar/db/metric/MetricDtoTest.java64
-rw-r--r--sonar-db/src/test/java/org/sonar/db/notification/NotificationQueueDaoTest.java85
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDaoTest.java137
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDtoTest.java54
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest.java118
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTest.java56
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/PermissionFacadeTest.java218
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java244
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDaoTest.java164
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDtoTest.java55
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTemplateDaoTest.java162
-rw-r--r--sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTest.java59
-rw-r--r--sonar-db/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java76
-rw-r--r--sonar-db/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java102
-rw-r--r--sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java340
-rw-r--r--sonar-db/src/test/java/org/sonar/db/property/PropertyDtoTest.java50
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/DbCleanerTestUtils.java44
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/IdUuidPairsTest.java53
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java153
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java76
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/PurgeDaoTest.java159
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/PurgeProfilerTest.java95
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/PurgeableSnapshotDtoTest.java56
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/period/DefaultPeriodCleanerTest.java93
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/period/DeleteAllFilterTest.java46
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/period/IntervalTest.java109
-rw-r--r--sonar-db/src/test/java/org/sonar/db/purge/period/KeepOneFilterTest.java93
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java83
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java55
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationQueryTest.java57
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDaoTest.java92
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDtoTest.java61
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateDaoTest.java86
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleDaoTest.java71
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleKeyTest.java101
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleParamDtoTest.java42
-rw-r--r--sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java255
-rw-r--r--sonar-db/src/test/java/org/sonar/db/rule/RuleDaoTest.java77
-rw-r--r--sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreDaoTest.java304
-rw-r--r--sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreUpdaterTest.java74
-rw-r--r--sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java51
-rw-r--r--sonar-db/src/test/java/org/sonar/db/source/FileSourceDtoTest.java49
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/AuthorDaoTest.java129
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/AuthorizationDaoTest.java295
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/GroupDtoTest.java42
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java299
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDtoTest.java60
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/GroupMembershipQueryTest.java57
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/GroupMembershipTest.java52
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/RoleDaoTest.java152
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/RoleMapperTest.java78
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java271
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/UserDtoTest.java44
-rw-r--r--sonar-db/src/test/java/org/sonar/db/version/DatabaseVersionTest.java53
-rw-r--r--sonar-db/src/test/java/org/sonar/db/version/v44/ChangeLogTest.java37
-rw-r--r--sonar-db/src/test/java/org/sonar/db/version/v44/ProfileMeasureTest.java37
-rw-r--r--sonar-db/src/test/java/org/sonar/db/version/v50/ComponentTest.java37
-rw-r--r--sonar-db/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java229
-rw-r--r--sonar-db/src/test/resources/logback-test.xml33
-rw-r--r--sonar-db/src/test/resources/org/sonar/api/database/configuration/DatabaseConfigurationTest/some-properties.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/qualitymodel/DefaultModelFinderTest/shared.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/fail_when_no_default_template_is_defined.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles-result.xml33
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles.xml26
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern-result.xml35
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern.xml31
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject-result.xml44
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject.xml35
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesSeveralPattern.xml31
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group-result.xml20
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group.xml19
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users-result.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users.xml20
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found-result.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted-result.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/hasRoles.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/core/user/DeprecatedUserFinderTest/fixture.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture-including-ghost-projects-and-technical-project.xml134
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture.xml86
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/getResources_exclude_disabled.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/get_last_snapshot_by_component_uuid.xml85
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert-result.xml17
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update-result.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date-result.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject-result.xml69
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject.xml34
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects-result.xml52
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects.xml31
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource.xml1
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource-result.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource.xml1
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotIndexPackages.xml31
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource-result.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource-result.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming-result.xml24
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource-result.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shared.xml72
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml73
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml72
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml64
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml63
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml74
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/empty.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldGetMaxOrderIndexForNullUser.xml21
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert-result.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsertWithNoUser-result.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForAnonymous.xml68
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForUser.xml85
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml68
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml85
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert-result.xml32
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml36
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldSelectGlobalDashboard.xml21
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/insert_characteristic-result.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics.xml24
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics_order_by_characteristic_order.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_max_characteristic_order_when_characteristics_are_all_disabled.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_sub_characteristics_by_parent_id.xml35
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/shared.xml26
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic-result.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/select_requirement.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/shared.xml17
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldGetByHash.xml61
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert-result.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/shared.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_key.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_keys.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_name_and_project_id.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_open_by_project_id.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_insert_new_action_plan-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/shared.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/should_find_by_project.xml81
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete-result.xml42
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete.xml38
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/empty.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/insert-result.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml182
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/shared.xml63
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update-result.xml38
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update.xml38
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_comment-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_diff-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/find_severities_by_component.xml80
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/shared.xml26
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_issue_and_component_ids.xml80
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module.xml133
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml127
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/shared.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_delete-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_insert-result.xml34
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user.xml33
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user_with_only_favorite_filters.xml39
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_provided_by_name.xml58
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_update-result.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/shared.xml21
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete-result.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_insert-result.xml27
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testInsert-result.xml29
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate-result.xml29
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate.xml27
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml30
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml28
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/issue/IssueStatsDaoTest/should_select_assignees.xml80
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldCountByTypeAndKey.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shared.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shouldInsert-result.xml34
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_findOldest.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions.xml20
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template-result.xml36
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template.xml31
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_count_component_permissions.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission-result.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions-result.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate-result.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate.xml1
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate-result.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate.xml1
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate-result.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectAllPermissionTemplates.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectEmptyPermissionTemplate.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectPermissionTemplate.xml21
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/select_only_enable_users.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions.xml17
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml22
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml22
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/findNotificationSubscribers.xml55
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml1
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectGlobalProperties.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectProperties.xml22
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml22
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml19
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_module_properties_tree.xml61
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/shouldFindUsersForNotification.xml46
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteResource.xml38
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml29
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml57
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot-result.xml87
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot.xml87
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml75
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml59
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources-result.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources.xml87
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot-result.xml118
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot.xml110
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/select_purgeable_file_uuids.xml88
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds-result.xml49
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds.xml45
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles-result.xml98
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles.xml94
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteProject.xml108
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots-result.xml37
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots.xml38
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject-result.xml49
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject.xml49
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml68
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml89
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues.xml94
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml86
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues.xml93
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest/shared.xml46
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/delete-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/selectForQualityGate.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions-result.xml24
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions.xml26
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/update-result.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/delete-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/selectAll.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/update-result.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete-result.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_profile-result.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_rule-result.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameter-result.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters-result.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id-result.xml19
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id.xml19
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/empty.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert_parameter-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/shared.xml22
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update-result.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update_parameter-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/delete-result.xml6
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/inheritance.xml23
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/insert-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/projects.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_all_is_sorted_by_profile_name.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_by_language.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/shared.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/update-result.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/empty.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert-result.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_all-result.xml47
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter-result.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectAll.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectById.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectNonManual.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectParameters.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_name.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_rule_key.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_sub_characteristic_id.xml74
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_enables_and_non_manual.xml31
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_id.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_ids.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update-result.xml27
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter-result.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter.xml3
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/semaphore/SemaphoreDaoTest/old_semaphore.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path-result.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path.xml2
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/countDeveloperLogins.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor.xml2
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper.xml2
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication-result.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication-result.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldSelectByLogin.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/anonymous_should_be_authorized.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/group_should_be_authorized.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/is_authorized_component_key_for_global_permission.xml15
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_anonymous.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_group.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_user.xml10
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_anonymous.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_group_anyone.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_group_global_permissions.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_anonymous.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_group.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_user.xml17
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_user_global_permissions.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/user_should_be_authorized.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/select_user_group.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared.xml19
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared_plus_empty_group.xml20
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/should_be_sorted_by_group_name.xml12
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId-result.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions-result.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions.xml18
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions-result.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions-result.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions.xml16
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions-result.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions.xml13
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/countRoles.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId-result.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles-result.xml11
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user-result.xml45
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user.xml43
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/insert-result.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectActiveUserByLogin.xml14
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectGroupByName.xml5
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByLogins.xml9
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByQuery.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByText.xml7
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/update_user.xml4
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion.xml8
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion_no_rows.xml1
-rw-r--r--sonar-db/src/test/resources/org/sonar/jpa/dao/ProfilesDaoTest/shouldGetProfiles.xml7
764 files changed, 48762 insertions, 0 deletions
diff --git a/sonar-db/pom.xml b/sonar-db/pom.xml
new file mode 100644
index 00000000000..422677b6342
--- /dev/null
+++ b/sonar-db/pom.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar</artifactId>
+ <version>5.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sonar-db</artifactId>
+
+ <name>SonarQube :: Database</name>
+ <description>Create and request SonarQube schema</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-batch-protocol</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mybatis</groupId>
+ <artifactId>mybatis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-dbcp</groupId>
+ <artifactId>commons-dbcp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-dbutils</groupId>
+ <artifactId>commons-dbutils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.jpountz.lz4</groupId>
+ <artifactId>lz4</artifactId>
+ </dependency>
+
+ <!-- logging -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </dependency>
+
+ <!-- tests -->
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.bean-matchers</groupId>
+ <artifactId>bean-matchers</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.simpleframework</groupId>
+ <artifactId>simple</artifactId>
+ <version>4.1.21</version>
+ <scope>test</scope>
+ </dependency>
+
+
+ <!--
+ JDBC drivers for MyBatis integration tests.
+ They can't be moved to the profile run-mybatis-its because
+ -->
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.sourceforge.jtds</groupId>
+ <artifactId>jtds</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sonar-db/src/main/java/org/sonar/batch/index/ResourceCopy.java b/sonar-db/src/main/java/org/sonar/batch/index/ResourceCopy.java
new file mode 100644
index 00000000000..6a3ccd83029
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/batch/index/ResourceCopy.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.index;
+
+/**
+ * Used by views !!
+ */
+public interface ResourceCopy {
+
+ int getCopyResourceId();
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/issue/ActionPlanStats.java b/sonar-db/src/main/java/org/sonar/core/issue/ActionPlanStats.java
new file mode 100644
index 00000000000..593ec0d63ca
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/issue/ActionPlanStats.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.issue;
+
+import java.util.Date;
+import org.sonar.api.issue.ActionPlan;
+import org.sonar.api.utils.internal.Uuids;
+
+public class ActionPlanStats extends DefaultActionPlan {
+
+ private int totalIssues;
+ private int unresolvedIssues;
+
+ private ActionPlanStats() {
+
+ }
+
+ public static ActionPlanStats create(String name) {
+ ActionPlanStats actionPlan = new ActionPlanStats();
+ actionPlan.setKey(Uuids.create());
+ Date now = new Date();
+ actionPlan.setName(name);
+ actionPlan.setStatus(ActionPlan.STATUS_OPEN);
+ actionPlan.setCreatedAt(now).setUpdatedAt(now);
+ return actionPlan;
+ }
+
+ public int totalIssues() {
+ return totalIssues;
+ }
+
+ public ActionPlanStats setTotalIssues(int totalIssues) {
+ this.totalIssues = totalIssues;
+ return this;
+ }
+
+ public int unresolvedIssues() {
+ return unresolvedIssues;
+ }
+
+ public ActionPlanStats setUnresolvedIssues(int unresolvedIssues) {
+ this.unresolvedIssues = unresolvedIssues;
+ return this;
+ }
+
+ public int resolvedIssues() {
+ return totalIssues - unresolvedIssues;
+ }
+
+ public boolean isOpen() {
+ return ActionPlan.STATUS_OPEN.equals(status());
+ }
+
+ public boolean overDue() {
+ Date deadline = deadLine();
+ return isOpen() && deadline != null && new Date().after(deadline);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/issue/db/package-info.java b/sonar-db/src/main/java/org/sonar/core/issue/db/package-info.java
new file mode 100644
index 00000000000..55dc4912649
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/issue/db/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.core.issue.db;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/core/issue/package-info.java b/sonar-db/src/main/java/org/sonar/core/issue/package-info.java
new file mode 100644
index 00000000000..0b6aa438498
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/issue/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.core.issue;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/core/permission/ComponentPermissions.java b/sonar-db/src/main/java/org/sonar/core/permission/ComponentPermissions.java
new file mode 100644
index 00000000000..4d2522cae47
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/permission/ComponentPermissions.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.permission;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.sonar.api.web.UserRole;
+
+/**
+ * Holds the constants representing the various component permissions that can be assigned to users & groups
+ *
+ */
+public final class ComponentPermissions {
+
+ /**
+ * All the component permissions values, ordered from {@link UserRole#USER} to {@link UserRole#CODEVIEWER}.
+ */
+ public static final List<String> ALL = ImmutableList.of(UserRole.USER, UserRole.ADMIN, UserRole.ISSUE_ADMIN, UserRole.CODEVIEWER);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/permission/GlobalPermissions.java b/sonar-db/src/main/java/org/sonar/core/permission/GlobalPermissions.java
new file mode 100644
index 00000000000..9f671e16794
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/permission/GlobalPermissions.java
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.permission;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+/**
+ * Holds the constants representing the various global permissions that can be assigned to users & groups
+ *
+ */
+public final class GlobalPermissions {
+
+ public static final String SYSTEM_ADMIN = "admin";
+ public static final String QUALITY_PROFILE_ADMIN = "profileadmin";
+ public static final String DASHBOARD_SHARING = "shareDashboard";
+ public static final String SCAN_EXECUTION = "scan";
+ public static final String PREVIEW_EXECUTION = "dryRunScan";
+ public static final String PROVISIONING = "provisioning";
+
+ /**
+ * All the global permissions values, ordered from {@link #SYSTEM_ADMIN} to {@link #PROVISIONING}.
+ */
+ public static final List<String> ALL = ImmutableList.of(SYSTEM_ADMIN, QUALITY_PROFILE_ADMIN, DASHBOARD_SHARING, SCAN_EXECUTION, PREVIEW_EXECUTION, PROVISIONING);
+
+ private GlobalPermissions() {
+ // only static methods
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/permission/GroupWithPermission.java b/sonar-db/src/main/java/org/sonar/core/permission/GroupWithPermission.java
new file mode 100644
index 00000000000..0dcc1d7ce1d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/permission/GroupWithPermission.java
@@ -0,0 +1,83 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.permission;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class GroupWithPermission {
+
+ private String name;
+ private String description;
+ private boolean hasPermission;
+
+ public String name() {
+ return name;
+ }
+
+ public GroupWithPermission setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String description() {
+ return description;
+ }
+
+ public GroupWithPermission setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public boolean hasPermission() {
+ return hasPermission;
+ }
+
+ public GroupWithPermission hasPermission(boolean hasPermission) {
+ this.hasPermission = hasPermission;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GroupWithPermission that = (GroupWithPermission) o;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/permission/UserWithPermission.java b/sonar-db/src/main/java/org/sonar/core/permission/UserWithPermission.java
new file mode 100644
index 00000000000..69a2f77a28a
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/permission/UserWithPermission.java
@@ -0,0 +1,79 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.permission;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class UserWithPermission {
+
+ private String login;
+ private String name;
+ private boolean hasPermission;
+
+ public String login() {
+ return login;
+ }
+
+ public UserWithPermission setLogin(String login) {
+ this.login = login;
+ return this;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public UserWithPermission setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public boolean hasPermission() {
+ return hasPermission;
+ }
+
+ public UserWithPermission hasPermission(boolean hasPermission) {
+ this.hasPermission = hasPermission;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ UserWithPermission that = (UserWithPermission) o;
+ return login.equals(that.login);
+ }
+
+ @Override
+ public int hashCode() {
+ return login.hashCode();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/permission/package-info.java b/sonar-db/src/main/java/org/sonar/core/permission/package-info.java
new file mode 100644
index 00000000000..7e8c924d2ca
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/permission/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.core.permission;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/core/timemachine/Periods.java b/sonar-db/src/main/java/org/sonar/core/timemachine/Periods.java
new file mode 100644
index 00000000000..2d16c7137d0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/timemachine/Periods.java
@@ -0,0 +1,195 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.timemachine;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.server.ServerSide;
+
+import static org.sonar.api.utils.DateUtils.longToDate;
+
+@ServerSide
+public class Periods {
+
+ private final Settings settings;
+ private final I18n i18n;
+
+ public Periods(Settings settings, I18n i18n) {
+ this.settings = settings;
+ this.i18n = i18n;
+ }
+
+ @CheckForNull
+ public String label(Snapshot snapshot, int periodIndex) {
+ return label(snapshot.getPeriodMode(periodIndex), snapshot.getPeriodModeParameter(periodIndex), longToDate(snapshot.getPeriodDateMs(periodIndex)));
+ }
+
+ @CheckForNull
+ public String abbreviation(Snapshot snapshot, int periodIndex) {
+ return abbreviation(snapshot.getPeriodMode(periodIndex), snapshot.getPeriodModeParameter(periodIndex), longToDate(snapshot.getPeriodDateMs(periodIndex)));
+ }
+
+ @CheckForNull
+ public String label(int periodIndex) {
+ String periodProperty = settings.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex);
+ PeriodParameters periodParameters = new PeriodParameters(periodProperty);
+ return label(periodParameters.getMode(), periodParameters.getParam(), periodParameters.getDate());
+ }
+
+ @CheckForNull
+ public String abbreviation(int periodIndex) {
+ String periodProperty = settings.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex);
+ PeriodParameters periodParameters = new PeriodParameters(periodProperty);
+ return abbreviation(periodParameters.getMode(), periodParameters.getParam(), periodParameters.getDate());
+ }
+
+ @CheckForNull
+ public String label(String mode, String param, Date date) {
+ return label(mode, param, convertDate(date), false);
+ }
+
+ @CheckForNull
+ public String label(String mode, String param, String date) {
+ return label(mode, param, date, false);
+ }
+
+ @CheckForNull
+ public String abbreviation(String mode, String param, Date date) {
+ return label(mode, param, convertDate(date), true);
+ }
+
+ @CheckForNull
+ private String label(String mode, @Nullable String param, @Nullable String date, boolean shortLabel) {
+ String label;
+ if (CoreProperties.TIMEMACHINE_MODE_DAYS.equals(mode)) {
+ label = label("over_x_days", shortLabel, param);
+ if (date != null) {
+ label = label("over_x_days_detailed", shortLabel, param, date);
+ }
+ } else if (CoreProperties.TIMEMACHINE_MODE_VERSION.equals(mode)) {
+ label = label("since_version", shortLabel, param);
+ if (date != null) {
+ label = label("since_version_detailed", shortLabel, param, date);
+ }
+ } else if (CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS.equals(mode)) {
+ label = label("since_previous_analysis", shortLabel);
+ if (date != null) {
+ label = label("since_previous_analysis_detailed", shortLabel, date);
+ }
+ } else if (CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION.equals(mode)) {
+ label = label("since_previous_version", shortLabel);
+ if (param != null) {
+ label = label("since_previous_version_detailed", shortLabel, param);
+ if (date != null) {
+ label = label("since_previous_version_detailed", shortLabel, param, date);
+ }
+ }
+ } else if (CoreProperties.TIMEMACHINE_MODE_DATE.equals(mode)) {
+ label = label("since_x", shortLabel, date);
+ } else {
+ throw new IllegalArgumentException("This mode is not supported : " + mode);
+ }
+ return label;
+ }
+
+ private String label(String key, boolean shortLabel, Object... parameters) {
+ String msgKey = key;
+ if (shortLabel) {
+ msgKey += ".short";
+ }
+ return i18n.message(getLocale(), msgKey, null, parameters);
+ }
+
+ @CheckForNull
+ private static String convertDate(Date date) {
+ if (date != null) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy MMM dd");
+ return dateFormat.format(date);
+ }
+ return null;
+ }
+
+ private static Locale getLocale() {
+ return Locale.ENGLISH;
+ }
+
+ private static class PeriodParameters {
+
+ private String mode = null;
+ private String param = null;
+ private Date date = null;
+
+ public PeriodParameters(String periodProperty) {
+ if (CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS.equals(periodProperty) || CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION.equals(periodProperty)) {
+ mode = periodProperty;
+ } else if (findByDays(periodProperty) != null) {
+ mode = CoreProperties.TIMEMACHINE_MODE_DAYS;
+ param = Integer.toString(findByDays(periodProperty));
+ } else if (findByDate(periodProperty) != null) {
+ mode = CoreProperties.TIMEMACHINE_MODE_DATE;
+ date = findByDate(periodProperty);
+ } else if (StringUtils.isNotBlank(periodProperty)) {
+ mode = CoreProperties.TIMEMACHINE_MODE_VERSION;
+ param = periodProperty;
+ } else {
+ throw new IllegalArgumentException("Unknown period property : " + periodProperty);
+ }
+ }
+
+ private static Integer findByDays(String property) {
+ try {
+ return Integer.parseInt(property);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ private static Date findByDate(String property) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ try {
+ return format.parse(property);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/timemachine/package-info.java b/sonar-db/src/main/java/org/sonar/core/timemachine/package-info.java
new file mode 100644
index 00000000000..039fb6393df
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/timemachine/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.core.timemachine;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/core/user/DefaultUserFinder.java b/sonar-db/src/main/java/org/sonar/core/user/DefaultUserFinder.java
new file mode 100644
index 00000000000..16d2867c6c0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/user/DefaultUserFinder.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.sonar.api.user.User;
+import org.sonar.api.user.UserFinder;
+import org.sonar.api.user.UserQuery;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserDto;
+
+/**
+ * @since 3.6
+ */
+public class DefaultUserFinder implements UserFinder {
+
+ private final UserDao userDao;
+
+ public DefaultUserFinder(UserDao userDao) {
+ this.userDao = userDao;
+ }
+
+ @Override
+ @CheckForNull
+ public User findByLogin(String login) {
+ UserDto dto = userDao.selectActiveUserByLogin(login);
+ return dto != null ? dto.toUser() : null;
+ }
+
+ @Override
+ public List<User> findByLogins(List<String> logins) {
+ List<UserDto> dtos = userDao.selectUsersByLogins(logins);
+ return toUsers(dtos);
+ }
+
+ @Override
+ public List<User> find(UserQuery query) {
+ List<UserDto> dtos = userDao.selectUsers(query);
+ return toUsers(dtos);
+ }
+
+ private List<User> toUsers(Collection<UserDto> dtos) {
+ List<User> users = Lists.newArrayList();
+ for (UserDto dto : dtos) {
+ users.add(dto.toUser());
+ }
+ return users;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/user/DeprecatedUserFinder.java b/sonar-db/src/main/java/org/sonar/core/user/DeprecatedUserFinder.java
new file mode 100644
index 00000000000..2413ce4babc
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/user/DeprecatedUserFinder.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import javax.annotation.Nullable;
+import org.sonar.api.database.model.User;
+import org.sonar.api.security.UserFinder;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserDto;
+
+/**
+ * @since 2.10
+ */
+public class DeprecatedUserFinder implements UserFinder {
+
+ private final UserDao userDao;
+
+ public DeprecatedUserFinder(UserDao userDao) {
+ this.userDao = userDao;
+ }
+
+ @Override
+ public User findById(int id) {
+ return copy(userDao.getUser(id));
+ }
+
+ @Override
+ public User findByLogin(String login) {
+ return copy(userDao.selectActiveUserByLogin(login));
+ }
+
+ private User copy(@Nullable UserDto dto) {
+ if (dto != null) {
+ User user = new User().setEmail(dto.getEmail()).setLogin(dto.getLogin()).setName(dto.getName());
+ user.setId(dto.getId().intValue());
+ return user;
+ }
+ return null;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/user/GroupMembership.java b/sonar-db/src/main/java/org/sonar/core/user/GroupMembership.java
new file mode 100644
index 00000000000..1e8715dead4
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/user/GroupMembership.java
@@ -0,0 +1,92 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class GroupMembership {
+
+ private Long id;
+ private String name;
+ private String description;
+ private boolean isMember;
+
+ public Long id() {
+ return id;
+ }
+
+ public GroupMembership setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public GroupMembership setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String description() {
+ return description;
+ }
+
+ public GroupMembership setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public boolean isMember() {
+ return isMember;
+ }
+
+ public GroupMembership setMember(boolean isMember) {
+ this.isMember = isMember;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GroupMembership that = (GroupMembership) o;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/core/user/package-info.java b/sonar-db/src/main/java/org/sonar/core/user/package-info.java
new file mode 100644
index 00000000000..73bfef46c33
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/core/user/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.core.user;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/BatchSession.java b/sonar-db/src/main/java/org/sonar/db/BatchSession.java
new file mode 100644
index 00000000000..3e6d0b89867
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/BatchSession.java
@@ -0,0 +1,221 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.executor.BatchResult;
+import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
+import org.apache.ibatis.executor.keygen.KeyGenerator;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.deprecated.ClusterAction;
+import org.sonar.db.deprecated.WorkQueue;
+
+public class BatchSession extends DbSession {
+
+ public static final int MAX_BATCH_SIZE = 250;
+
+ private final int batchSize;
+ private int count = 0;
+
+ BatchSession(WorkQueue<?> queue, SqlSession session) {
+ this(queue, session, MAX_BATCH_SIZE);
+ }
+
+ BatchSession(WorkQueue<?> queue, SqlSession session, int batchSize) {
+ super(queue, session);
+ this.batchSize = batchSize;
+ }
+
+ @Override
+ public void enqueue(ClusterAction action) {
+ increment();
+ super.enqueue(action);
+ }
+
+ @Override
+ public void select(String statement, Object parameter, ResultHandler handler) {
+ reset();
+ super.select(statement, parameter, handler);
+ }
+
+ @Override
+ public void select(String statement, ResultHandler handler) {
+ reset();
+ super.select(statement, handler);
+ }
+
+ @Override
+ public <T> T selectOne(String statement) {
+ reset();
+ return super.selectOne(statement);
+ }
+
+ @Override
+ public <T> T selectOne(String statement, Object parameter) {
+ reset();
+ return super.selectOne(statement, parameter);
+ }
+
+ @Override
+ public <E> List<E> selectList(String statement) {
+ reset();
+ return super.selectList(statement);
+ }
+
+ @Override
+ public <E> List<E> selectList(String statement, Object parameter) {
+ reset();
+ return super.selectList(statement, parameter);
+ }
+
+ @Override
+ public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
+ reset();
+ return super.selectList(statement, parameter, rowBounds);
+ }
+
+ @Override
+ public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
+ reset();
+ return super.selectMap(statement, mapKey);
+ }
+
+ @Override
+ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
+ reset();
+ return super.selectMap(statement, parameter, mapKey);
+ }
+
+ @Override
+ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
+ reset();
+ return super.selectMap(statement, parameter, mapKey, rowBounds);
+ }
+
+ @Override
+ public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
+ reset();
+ super.select(statement, parameter, rowBounds, handler);
+ }
+
+ @Override
+ public int insert(String statement) {
+ makeSureGeneratedKeysAreNotUsedInBatchInserts(statement);
+ increment();
+ return super.insert(statement);
+ }
+
+ @Override
+ public int insert(String statement, Object parameter) {
+ makeSureGeneratedKeysAreNotUsedInBatchInserts(statement);
+ increment();
+ return super.insert(statement, parameter);
+ }
+
+ private void makeSureGeneratedKeysAreNotUsedInBatchInserts(String statement) {
+ Configuration configuration = super.getConfiguration();
+ if (null != configuration) {
+ MappedStatement mappedStatement = configuration.getMappedStatement(statement);
+ if (null != mappedStatement) {
+ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
+ if (keyGenerator instanceof Jdbc3KeyGenerator) {
+ throw new IllegalStateException("Batch inserts cannot use generated keys");
+ }
+ }
+ }
+ }
+
+ @Override
+ public int update(String statement) {
+ increment();
+ return super.update(statement);
+ }
+
+ @Override
+ public int update(String statement, Object parameter) {
+ increment();
+ return super.update(statement, parameter);
+ }
+
+ @Override
+ public int delete(String statement) {
+ increment();
+ return super.delete(statement);
+ }
+
+ @Override
+ public int delete(String statement, Object parameter) {
+ increment();
+ return super.delete(statement, parameter);
+ }
+
+ @Override
+ public void commit() {
+ super.commit();
+ reset();
+ }
+
+ @Override
+ public void commit(boolean force) {
+ super.commit(force);
+ reset();
+ }
+
+ @Override
+ public void rollback() {
+ super.rollback();
+ reset();
+ }
+
+ @Override
+ public void rollback(boolean force) {
+ super.rollback(force);
+ reset();
+ }
+
+ @Override
+ public List<BatchResult> flushStatements() {
+ List<BatchResult> batchResults = super.flushStatements();
+ reset();
+ return batchResults;
+ }
+
+ @Override
+ public <T> T getMapper(Class<T> type) {
+ return getConfiguration().getMapper(type, this);
+ }
+
+ private BatchSession increment() {
+ count += 1;
+ if (count >= batchSize) {
+ commit();
+ }
+ return this;
+ }
+
+ private void reset() {
+ count = 0;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/Dao.java b/sonar-db/src/main/java/org/sonar/db/Dao.java
new file mode 100644
index 00000000000..99bd33d1eb5
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/Dao.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+public interface Dao {
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/DaoUtils.java b/sonar-db/src/main/java/org/sonar/db/DaoUtils.java
new file mode 100644
index 00000000000..5c0d3269e3c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/DaoUtils.java
@@ -0,0 +1,149 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceIndexerDao;
+import org.sonar.db.component.ResourceKeyUpdaterDao;
+import org.sonar.db.dashboard.ActiveDashboardDao;
+import org.sonar.db.dashboard.DashboardDao;
+import org.sonar.db.debt.CharacteristicDao;
+import org.sonar.db.duplication.DuplicationDao;
+import org.sonar.db.issue.ActionPlanDao;
+import org.sonar.db.issue.ActionPlanStatsDao;
+import org.sonar.db.issue.IssueChangeDao;
+import org.sonar.db.issue.IssueDao;
+import org.sonar.db.issue.IssueFilterDao;
+import org.sonar.db.issue.IssueFilterFavouriteDao;
+import org.sonar.db.loadedtemplate.LoadedTemplateDao;
+import org.sonar.db.notification.NotificationQueueDao;
+import org.sonar.db.permission.PermissionDao;
+import org.sonar.db.permission.PermissionTemplateDao;
+import org.sonar.db.property.PropertiesDao;
+import org.sonar.db.purge.PurgeDao;
+import org.sonar.db.qualitygate.QualityGateConditionDao;
+import org.sonar.db.qualityprofile.ActiveRuleDao;
+import org.sonar.db.qualityprofile.QualityProfileDao;
+import org.sonar.db.rule.RuleDao;
+import org.sonar.db.semaphore.SemaphoreDao;
+import org.sonar.db.user.AuthorDao;
+import org.sonar.db.user.AuthorizationDao;
+import org.sonar.db.user.GroupMembershipDao;
+import org.sonar.db.user.RoleDao;
+import org.sonar.db.user.UserDao;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public final class DaoUtils {
+
+ private static final int PARTITION_SIZE_FOR_ORACLE = 1000;
+
+ private DaoUtils() {
+ // only static stuff
+ }
+
+ public static List<Class> getDaoClasses() {
+ return ImmutableList.<Class>of(
+ ActionPlanDao.class,
+ ActionPlanStatsDao.class,
+ ActiveDashboardDao.class,
+ ActiveRuleDao.class,
+ AuthorDao.class,
+ AuthorizationDao.class,
+ DashboardDao.class,
+ DuplicationDao.class,
+ GroupMembershipDao.class,
+ IssueDao.class,
+ IssueChangeDao.class,
+ IssueFilterDao.class,
+ IssueFilterFavouriteDao.class,
+ LoadedTemplateDao.class,
+ NotificationQueueDao.class,
+ PermissionDao.class,
+ PermissionTemplateDao.class,
+ PropertiesDao.class,
+ QualityGateConditionDao.class,
+ QualityProfileDao.class,
+ PurgeDao.class,
+ CharacteristicDao.class,
+ ResourceIndexerDao.class,
+ ResourceDao.class,
+ ResourceKeyUpdaterDao.class,
+ RoleDao.class,
+ RuleDao.class,
+ SemaphoreDao.class,
+ UserDao.class
+ );
+ }
+
+ /**
+ * Partition by 1000 elements a list of input and execute a function on each part.
+ *
+ * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)'
+ * and with MsSQL when there's more than 2000 parameters in a query
+ */
+ public static <OUTPUT, INPUT> List<OUTPUT> executeLargeInputs(Collection<INPUT> input, Function<List<INPUT>, List<OUTPUT>> function) {
+ if (input.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<OUTPUT> results = newArrayList();
+ List<List<INPUT>> partitionList = Lists.partition(newArrayList(input), PARTITION_SIZE_FOR_ORACLE);
+ for (List<INPUT> partition : partitionList) {
+ List<OUTPUT> subResults = function.apply(partition);
+ results.addAll(subResults);
+ }
+ return results;
+ }
+
+ /**
+ * Partition by 1000 elements a list of input and execute a function on each part.
+ * The function has not output (ex: delete operation)
+ *
+ * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)'
+ * and with MsSQL when there's more than 2000 parameters in a query
+ */
+ public static <INPUT> void executeLargeInputsWithoutOutput(Collection<INPUT> input, Function<List<INPUT>, Void> function) {
+ if (input.isEmpty()) {
+ return;
+ }
+
+ List<List<INPUT>> partitions = Lists.partition(newArrayList(input), PARTITION_SIZE_FOR_ORACLE);
+ for (List<INPUT> partition : partitions) {
+ function.apply(partition);
+ }
+ }
+
+ public static String repeatCondition(String sql, int count, String separator) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ sb.append(sql);
+ if (i < count - 1) {
+ sb.append(" ").append(separator).append(" ");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/Database.java b/sonar-db/src/main/java/org/sonar/db/Database.java
new file mode 100644
index 00000000000..6b747edee76
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/Database.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import javax.sql.DataSource;
+import org.picocontainer.Startable;
+import org.sonar.db.dialect.Dialect;
+
+/**
+ * @since 2.12
+ */
+public interface Database extends Startable {
+ /**
+ * Returns the configured datasource. Null as long as start() is not executed.
+ */
+ DataSource getDataSource();
+
+ /**
+ * @return the dialect or null if start() has not been executed
+ */
+ Dialect getDialect();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java b/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java
new file mode 100644
index 00000000000..28ac5923b72
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import javax.annotation.Nullable;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @since 2.13
+ */
+public final class DatabaseUtils {
+ private DatabaseUtils() {
+ // only static methods
+ }
+
+ public static void closeQuietly(@Nullable Connection connection) {
+ if (connection != null) {
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close connection", e);
+ // ignore
+ }
+ }
+ }
+
+ public static void closeQuietly(@Nullable Statement stmt) {
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close statement", e);
+ // ignore
+ }
+ }
+ }
+
+ public static void closeQuietly(@Nullable ResultSet rs) {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close result set", e);
+ // ignore
+ }
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/DbSession.java b/sonar-db/src/main/java/org/sonar/db/DbSession.java
new file mode 100644
index 00000000000..d5760462d52
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/DbSession.java
@@ -0,0 +1,201 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.executor.BatchResult;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.deprecated.ClusterAction;
+import org.sonar.db.deprecated.WorkQueue;
+
+public class DbSession implements SqlSession {
+
+ private List<ClusterAction> actions;
+
+ private WorkQueue queue;
+ private SqlSession session;
+ private int actionCount;
+
+ DbSession(WorkQueue queue, SqlSession session) {
+ this.actionCount = 0;
+ this.session = session;
+ this.queue = queue;
+ this.actions = new ArrayList<>();
+ }
+
+ public void enqueue(ClusterAction action) {
+ actionCount++;
+ this.actions.add(action);
+ }
+
+ public int getActionCount() {
+ return actionCount;
+ }
+
+ @Override
+ public void commit() {
+ session.commit();
+ queue.enqueue(actions);
+ actions.clear();
+ }
+
+ @Override
+ public void commit(boolean force) {
+ session.commit(force);
+ queue.enqueue(actions);
+ actions.clear();
+ }
+
+ /**
+ * We only care about the the commit section.
+ * The rest is simply passed to its parent.
+ */
+
+ @Override
+ public <T> T selectOne(String statement) {
+ return session.selectOne(statement);
+ }
+
+ @Override
+ public <T> T selectOne(String statement, Object parameter) {
+ return session.selectOne(statement, parameter);
+ }
+
+ @Override
+ public <E> List<E> selectList(String statement) {
+ return session.selectList(statement);
+ }
+
+ @Override
+ public <E> List<E> selectList(String statement, Object parameter) {
+ return session.selectList(statement, parameter);
+ }
+
+ @Override
+ public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
+ return session.selectList(statement, parameter, rowBounds);
+ }
+
+ @Override
+ public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
+ return session.selectMap(statement, mapKey);
+ }
+
+ @Override
+ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
+ return session.selectMap(statement, parameter, mapKey);
+ }
+
+ @Override
+ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
+ return session.selectMap(statement, parameter, mapKey, rowBounds);
+ }
+
+ @Override
+ public void select(String statement, Object parameter, ResultHandler handler) {
+ session.select(statement, parameter, handler);
+ }
+
+ @Override
+ public void select(String statement, ResultHandler handler) {
+ session.select(statement, handler);
+ }
+
+ @Override
+ public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
+ session.select(statement, parameter, rowBounds, handler);
+ }
+
+ @Override
+ public int insert(String statement) {
+ return session.insert(statement);
+ }
+
+ @Override
+ public int insert(String statement, Object parameter) {
+ return session.insert(statement, parameter);
+ }
+
+ @Override
+ public int update(String statement) {
+ return session.update(statement);
+ }
+
+ @Override
+ public int update(String statement, Object parameter) {
+ return session.update(statement, parameter);
+ }
+
+ @Override
+ public int delete(String statement) {
+ return session.delete(statement);
+ }
+
+ @Override
+ public int delete(String statement, Object parameter) {
+ return session.delete(statement, parameter);
+ }
+
+ @Override
+ public void rollback() {
+ session.rollback();
+ }
+
+ @Override
+ public void rollback(boolean force) {
+ session.rollback(force);
+ }
+
+ @Override
+ public List<BatchResult> flushStatements() {
+ return session.flushStatements();
+ }
+
+ @Override
+ public void close() {
+ session.close();
+ }
+
+ @Override
+ public void clearCache() {
+ session.clearCache();
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return session.getConfiguration();
+ }
+
+ @Override
+ public <T> T getMapper(Class<T> type) {
+ return session.getMapper(type);
+ }
+
+ @Override
+ public Connection getConnection() {
+ return session.getConnection();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/DdlUtils.java b/sonar-db/src/main/java/org/sonar/db/DdlUtils.java
new file mode 100644
index 00000000000..52e64e59728
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/DdlUtils.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import org.apache.commons.io.output.NullWriter;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.jdbc.ScriptRunner;
+
+/**
+ * Util class to create Sonar database tables
+ *
+ * @since 2.12
+ */
+public final class DdlUtils {
+
+ private DdlUtils() {
+ }
+
+ public static boolean supportsDialect(String dialect) {
+ return "h2".equals(dialect);
+ }
+
+ /**
+ * The connection is commited in this method but not closed.
+ */
+ public static void createSchema(Connection connection, String dialect) {
+ executeScript(connection, "org/sonar/db/version/schema-" + dialect + ".ddl");
+ executeScript(connection, "org/sonar/db/version/rows-" + dialect + ".sql");
+ }
+
+ public static void executeScript(Connection connection, String path) {
+ ScriptRunner scriptRunner = newScriptRunner(connection);
+ try {
+ scriptRunner.runScript(Resources.getResourceAsReader(path));
+ connection.commit();
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to restore: " + path, e);
+ }
+ }
+
+ private static ScriptRunner newScriptRunner(Connection connection) {
+ ScriptRunner scriptRunner = new ScriptRunner(connection);
+ scriptRunner.setDelimiter(";");
+ scriptRunner.setStopOnError(true);
+ scriptRunner.setLogWriter(new PrintWriter(new NullWriter()));
+ return scriptRunner;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java b/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java
new file mode 100644
index 00000000000..c5e5e1c12b8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java
@@ -0,0 +1,188 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.sql.DataSource;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbcp.BasicDataSourceFactory;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.DatabaseProperties;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.DialectUtils;
+import org.sonar.db.profiling.ProfiledDataSource;
+
+/**
+ * @since 2.12
+ */
+public class DefaultDatabase implements Database {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Database.class);
+
+ private static final String DEFAULT_URL = "jdbc:h2:tcp://localhost/sonar";
+ private static final String SONAR_JDBC = "sonar.jdbc.";
+ private static final String SONAR_JDBC_DIALECT = "sonar.jdbc.dialect";
+ private static final String SONAR_JDBC_URL = "sonar.jdbc.url";
+
+ private Settings settings;
+ private BasicDataSource datasource;
+ private Dialect dialect;
+ private Properties properties;
+
+ public DefaultDatabase(Settings settings) {
+ this.settings = settings;
+ }
+
+ @Override
+ public void start() {
+ try {
+ initSettings();
+ initDatasource();
+ checkConnection();
+ doAfterStart();
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to connect to database", e);
+ }
+ }
+
+ /**
+ * Override to execute post-startup code.
+ */
+ protected void doAfterStart() {
+ // nothing to do
+ }
+
+ @VisibleForTesting
+ void initSettings() {
+ properties = new Properties();
+ completeProperties(settings, properties, SONAR_JDBC);
+ completeDefaultProperties(properties);
+ doCompleteProperties(properties);
+
+ dialect = DialectUtils.find(properties.getProperty(SONAR_JDBC_DIALECT), properties.getProperty(SONAR_JDBC_URL));
+ properties.setProperty(DatabaseProperties.PROP_DRIVER, dialect.getDefaultDriverClassName());
+ }
+
+ private void initDatasource() throws Exception {// NOSONAR this exception is thrown by BasicDataSourceFactory
+ // but it's correctly caught by start()
+ LOG.info("Create JDBC datasource for " + properties.getProperty(DatabaseProperties.PROP_URL, DEFAULT_URL));
+ datasource = (BasicDataSource) BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties));
+ datasource.setConnectionInitSqls(dialect.getConnectionInitStatements());
+ datasource.setValidationQuery(dialect.getValidationQuery());
+ if ("TRACE".equals(settings.getString("sonar.log.level"))) {
+ datasource = new ProfiledDataSource(datasource);
+ }
+ }
+
+ private void checkConnection() {
+ Connection connection = null;
+ try {
+ LOG.debug("Testing JDBC connection");
+ connection = datasource.getConnection();
+ } catch (SQLException e) {
+ throw new IllegalStateException("Can not connect to database. Please check connectivity and settings (see the properties prefixed by 'sonar.jdbc.').", e);
+ } finally {
+ DbUtils.closeQuietly(connection);
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (datasource != null) {
+ try {
+ datasource.close();
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to stop JDBC connection pool", e);
+ }
+ }
+ }
+
+ @Override
+ public final Dialect getDialect() {
+ return dialect;
+ }
+
+ @Override
+ public final DataSource getDataSource() {
+ return datasource;
+ }
+
+ public final Properties getProperties() {
+ return properties;
+ }
+
+ /**
+ * Override this method to add JDBC properties at runtime
+ */
+ protected void doCompleteProperties(Properties properties) {
+ // open-close principle
+ }
+
+ private static void completeProperties(Settings settings, Properties properties, String prefix) {
+ List<String> jdbcKeys = settings.getKeysStartingWith(prefix);
+ for (String jdbcKey : jdbcKeys) {
+ String value = settings.getString(jdbcKey);
+ properties.setProperty(jdbcKey, value);
+ }
+ }
+
+ @VisibleForTesting
+ static Properties extractCommonsDbcpProperties(Properties properties) {
+ Properties result = new Properties();
+ for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+ String key = (String) entry.getKey();
+ if (StringUtils.startsWith(key, SONAR_JDBC)) {
+ result.setProperty(StringUtils.removeStart(key, SONAR_JDBC), (String) entry.getValue());
+ }
+ }
+
+ // This property is required by the Ruby Oracle enhanced adapter.
+ // It directly uses the Connection implementation provided by the Oracle driver
+ result.setProperty("accessToUnderlyingConnectionAllowed", "true");
+ return result;
+ }
+
+ private static void completeDefaultProperties(Properties props) {
+ completeDefaultProperty(props, DatabaseProperties.PROP_URL, DEFAULT_URL);
+ completeDefaultProperty(props, DatabaseProperties.PROP_USER, props.getProperty(DatabaseProperties.PROP_USER_DEPRECATED, DatabaseProperties.PROP_USER_DEFAULT_VALUE));
+ completeDefaultProperty(props, DatabaseProperties.PROP_PASSWORD, DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE);
+ }
+
+ private static void completeDefaultProperty(Properties props, String key, String defaultValue) {
+ if (props.getProperty(key) == null && defaultValue != null) {
+ props.setProperty(key, defaultValue);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Database[" + (properties != null ? properties.getProperty(SONAR_JDBC_URL) : "?") + "]";
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/Dto.java b/sonar-db/src/main/java/org/sonar/db/Dto.java
new file mode 100644
index 00000000000..e5b9c494e97
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/Dto.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public abstract class Dto<K extends Serializable> {
+
+ private Date createdAt;
+ private Date updatedAt;
+
+ public abstract K getKey();
+
+ public Dto setCreatedAt(Date datetime) {
+ this.createdAt = datetime;
+ return this;
+ }
+
+ public Dto setUpdatedAt(Date datetime) {
+ this.updatedAt = datetime;
+ return this;
+ }
+
+ public final Date getCreatedAt() {
+ return this.createdAt;
+ }
+
+ public final Date getUpdatedAt() {
+ return this.updatedAt;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/IsAliveMapper.java b/sonar-db/src/main/java/org/sonar/db/IsAliveMapper.java
new file mode 100644
index 00000000000..75c63cf70a1
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/IsAliveMapper.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+public interface IsAliveMapper {
+ int IS_ALIVE_RETURNED_VALUE = 1;
+
+ /**
+ * Always return {@link #IS_ALIVE_RETURNED_VALUE} unless a database or connection error occurs.
+ */
+ int isAlive();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/MyBatis.java b/sonar-db/src/main/java/org/sonar/db/MyBatis.java
new file mode 100644
index 00000000000..a6aa01cbc4f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/MyBatis.java
@@ -0,0 +1,326 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db;
+
+import ch.qos.logback.classic.Level;
+import com.google.common.io.Closeables;
+import java.io.InputStream;
+import javax.annotation.Nullable;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.ibatis.type.JdbcType;
+import org.slf4j.LoggerFactory;
+import org.sonar.db.activity.ActivityDto;
+import org.sonar.db.activity.ActivityMapper;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentIndexMapper;
+import org.sonar.db.component.ComponentLinkDto;
+import org.sonar.db.component.ComponentLinkMapper;
+import org.sonar.db.component.ComponentMapper;
+import org.sonar.db.component.FilePathWithHashDto;
+import org.sonar.db.component.ResourceDto;
+import org.sonar.db.component.ResourceIndexDto;
+import org.sonar.db.component.ResourceIndexerMapper;
+import org.sonar.db.component.ResourceKeyUpdaterMapper;
+import org.sonar.db.component.ResourceMapper;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotMapper;
+import org.sonar.db.component.UuidWithProjectUuidDto;
+import org.sonar.db.compute.AnalysisReportDto;
+import org.sonar.db.compute.AnalysisReportMapper;
+import org.sonar.db.dashboard.ActiveDashboardDto;
+import org.sonar.db.dashboard.ActiveDashboardMapper;
+import org.sonar.db.dashboard.DashboardDto;
+import org.sonar.db.dashboard.DashboardMapper;
+import org.sonar.db.dashboard.WidgetDto;
+import org.sonar.db.dashboard.WidgetMapper;
+import org.sonar.db.dashboard.WidgetPropertyDto;
+import org.sonar.db.dashboard.WidgetPropertyMapper;
+import org.sonar.db.debt.CharacteristicDto;
+import org.sonar.db.debt.CharacteristicMapper;
+import org.sonar.db.debt.RequirementMigrationDto;
+import org.sonar.db.deprecated.WorkQueue;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.duplication.DuplicationMapper;
+import org.sonar.db.duplication.DuplicationUnitDto;
+import org.sonar.db.event.EventDto;
+import org.sonar.db.event.EventMapper;
+import org.sonar.db.issue.ActionPlanDto;
+import org.sonar.db.issue.ActionPlanMapper;
+import org.sonar.db.issue.ActionPlanStatsDto;
+import org.sonar.db.issue.ActionPlanStatsMapper;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueChangeMapper;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.IssueFilterDto;
+import org.sonar.db.issue.IssueFilterFavouriteDto;
+import org.sonar.db.issue.IssueFilterFavouriteMapper;
+import org.sonar.db.issue.IssueFilterMapper;
+import org.sonar.db.issue.IssueMapper;
+import org.sonar.db.loadedtemplate.LoadedTemplateDto;
+import org.sonar.db.loadedtemplate.LoadedTemplateMapper;
+import org.sonar.db.measure.CustomMeasureDto;
+import org.sonar.db.measure.CustomMeasureMapper;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.measure.MeasureFilterDto;
+import org.sonar.db.measure.MeasureFilterMapper;
+import org.sonar.db.measure.MeasureMapper;
+import org.sonar.db.metric.MetricMapper;
+import org.sonar.db.notification.NotificationQueueDto;
+import org.sonar.db.notification.NotificationQueueMapper;
+import org.sonar.db.permission.GroupWithPermissionDto;
+import org.sonar.db.permission.PermissionTemplateDto;
+import org.sonar.db.permission.PermissionTemplateGroupDto;
+import org.sonar.db.permission.PermissionTemplateMapper;
+import org.sonar.db.permission.PermissionTemplateUserDto;
+import org.sonar.db.permission.UserWithPermissionDto;
+import org.sonar.db.property.PropertiesMapper;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.db.purge.IdUuidPair;
+import org.sonar.db.purge.PurgeMapper;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
+import org.sonar.db.qualitygate.ProjectQgateAssociationMapper;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+import org.sonar.db.qualitygate.QualityGateConditionMapper;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.qualitygate.QualityGateMapper;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleMapper;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QualityProfileDto;
+import org.sonar.db.qualityprofile.QualityProfileMapper;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleMapper;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.semaphore.SemaphoreDto;
+import org.sonar.db.semaphore.SemaphoreMapper;
+import org.sonar.db.source.FileSourceMapper;
+import org.sonar.db.user.AuthorDto;
+import org.sonar.db.user.AuthorMapper;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.GroupMapper;
+import org.sonar.db.user.GroupMembershipDto;
+import org.sonar.db.user.GroupMembershipMapper;
+import org.sonar.db.user.GroupRoleDto;
+import org.sonar.db.user.RoleMapper;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserGroupDto;
+import org.sonar.db.user.UserGroupMapper;
+import org.sonar.db.user.UserMapper;
+import org.sonar.db.user.UserRoleDto;
+import org.sonar.db.version.SchemaMigrationDto;
+import org.sonar.db.version.SchemaMigrationMapper;
+import org.sonar.db.version.v44.Migration44Mapper;
+import org.sonar.db.version.v45.Migration45Mapper;
+import org.sonar.db.version.v50.Migration50Mapper;
+
+public class MyBatis {
+
+ private final Database database;
+ private SqlSessionFactory sessionFactory;
+
+ // TODO this queue should directly be an IndexQueue. Pending move of persistence to sonar-server
+ private WorkQueue<?> queue;
+
+ public MyBatis(Database database, WorkQueue<?> queue) {
+ this.database = database;
+ this.queue = queue;
+ }
+
+ public static void closeQuietly(@Nullable SqlSession session) {
+ if (session != null) {
+ try {
+ session.close();
+ } catch (Exception e) {
+ LoggerFactory.getLogger(MyBatis.class).warn("Fail to close session", e);
+ // do not re-throw the exception
+ }
+ }
+ }
+
+ private static JdbcTransactionFactory createTransactionFactory() {
+ return new JdbcTransactionFactory();
+ }
+
+ public MyBatis start() {
+ LogFactory.useSlf4jLogging();
+
+ Configuration conf = new Configuration();
+ conf.setEnvironment(new Environment("production", createTransactionFactory(), database.getDataSource()));
+ conf.setUseGeneratedKeys(true);
+ conf.setLazyLoadingEnabled(false);
+ conf.setJdbcTypeForNull(JdbcType.NULL);
+ Dialect dialect = database.getDialect();
+ conf.setDatabaseId(dialect.getId());
+ conf.getVariables().setProperty("_true", dialect.getTrueSqlValue());
+ conf.getVariables().setProperty("_false", dialect.getFalseSqlValue());
+ conf.getVariables().setProperty("_scrollFetchSize", String.valueOf(dialect.getScrollDefaultFetchSize()));
+
+ loadAlias(conf, "ActiveDashboard", ActiveDashboardDto.class);
+ loadAlias(conf, "Author", AuthorDto.class);
+ loadAlias(conf, "Component", ComponentDto.class);
+ loadAlias(conf, "ComponentLink", ComponentLinkDto.class);
+ loadAlias(conf, "Dashboard", DashboardDto.class);
+ loadAlias(conf, "DuplicationUnit", DuplicationUnitDto.class);
+ loadAlias(conf, "Group", GroupDto.class);
+ loadAlias(conf, "GroupRole", GroupRoleDto.class);
+ loadAlias(conf, "GroupMembership", GroupMembershipDto.class);
+ loadAlias(conf, "LoadedTemplate", LoadedTemplateDto.class);
+ loadAlias(conf, "MeasureFilter", MeasureFilterDto.class);
+ loadAlias(conf, "NotificationQueue", NotificationQueueDto.class);
+ loadAlias(conf, "Property", PropertyDto.class);
+ loadAlias(conf, "PurgeableSnapshot", PurgeableSnapshotDto.class);
+ loadAlias(conf, "QualityGate", QualityGateDto.class);
+ loadAlias(conf, "QualityGateCondition", QualityGateConditionDto.class);
+ loadAlias(conf, "ProjectQgateAssociation", ProjectQgateAssociationDto.class);
+ loadAlias(conf, "Resource", ResourceDto.class);
+ loadAlias(conf, "ResourceIndex", ResourceIndexDto.class);
+ loadAlias(conf, "Rule", RuleDto.class);
+ loadAlias(conf, "RuleParam", RuleParamDto.class);
+ loadAlias(conf, "Snapshot", SnapshotDto.class);
+ loadAlias(conf, "Semaphore", SemaphoreDto.class);
+ loadAlias(conf, "SchemaMigration", SchemaMigrationDto.class);
+ loadAlias(conf, "User", UserDto.class);
+ loadAlias(conf, "UserRole", UserRoleDto.class);
+ loadAlias(conf, "UserGroup", UserGroupDto.class);
+ loadAlias(conf, "Widget", WidgetDto.class);
+ loadAlias(conf, "WidgetProperty", WidgetPropertyDto.class);
+ loadAlias(conf, "Measure", MeasureDto.class);
+ loadAlias(conf, "Issue", IssueDto.class);
+ loadAlias(conf, "IssueChange", IssueChangeDto.class);
+ loadAlias(conf, "IssueFilter", IssueFilterDto.class);
+ loadAlias(conf, "IssueFilterFavourite", IssueFilterFavouriteDto.class);
+ loadAlias(conf, "ActionPlanIssue", ActionPlanDto.class);
+ loadAlias(conf, "ActionPlanStats", ActionPlanStatsDto.class);
+ loadAlias(conf, "PermissionTemplate", PermissionTemplateDto.class);
+ loadAlias(conf, "PermissionTemplateUser", PermissionTemplateUserDto.class);
+ loadAlias(conf, "PermissionTemplateGroup", PermissionTemplateGroupDto.class);
+ loadAlias(conf, "Characteristic", CharacteristicDto.class);
+ loadAlias(conf, "UserWithPermission", UserWithPermissionDto.class);
+ loadAlias(conf, "GroupWithPermission", GroupWithPermissionDto.class);
+ loadAlias(conf, "QualityProfile", QualityProfileDto.class);
+ loadAlias(conf, "ActiveRule", ActiveRuleDto.class);
+ loadAlias(conf, "ActiveRuleParam", ActiveRuleParamDto.class);
+ loadAlias(conf, "RequirementMigration", RequirementMigrationDto.class);
+ loadAlias(conf, "Activity", ActivityDto.class);
+ loadAlias(conf, "AnalysisReport", AnalysisReportDto.class);
+ loadAlias(conf, "IdUuidPair", IdUuidPair.class);
+ loadAlias(conf, "FilePathWithHash", FilePathWithHashDto.class);
+ loadAlias(conf, "UuidWithProjectUuid", UuidWithProjectUuidDto.class);
+ loadAlias(conf, "Event", EventDto.class);
+ loadAlias(conf, "CustomMeasure", CustomMeasureDto.class);
+
+ // AuthorizationMapper has to be loaded before IssueMapper because this last one used it
+ loadMapper(conf, "org.sonar.db.user.AuthorizationMapper");
+ // ResourceMapper has to be loaded before IssueMapper because this last one used it
+ loadMapper(conf, ResourceMapper.class);
+
+ loadMapper(conf, "org.sonar.db.permission.PermissionMapper");
+ Class<?>[] mappers = {ActivityMapper.class, ActiveDashboardMapper.class, AuthorMapper.class, DashboardMapper.class,
+ DuplicationMapper.class,
+ IssueMapper.class, IssueChangeMapper.class, IssueFilterMapper.class, IssueFilterFavouriteMapper.class,
+ IsAliveMapper.class,
+ LoadedTemplateMapper.class, MeasureFilterMapper.class, Migration44Mapper.class, PermissionTemplateMapper.class, PropertiesMapper.class, PurgeMapper.class,
+ ResourceKeyUpdaterMapper.class, ResourceIndexerMapper.class, RoleMapper.class, RuleMapper.class,
+ SchemaMigrationMapper.class, SemaphoreMapper.class, UserMapper.class, GroupMapper.class, UserGroupMapper.class, WidgetMapper.class, WidgetPropertyMapper.class,
+ FileSourceMapper.class, ActionPlanMapper.class,
+ ActionPlanStatsMapper.class,
+ NotificationQueueMapper.class, CharacteristicMapper.class,
+ GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class,
+ MeasureMapper.class, MetricMapper.class, CustomMeasureMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, SnapshotMapper.class,
+ ProjectQgateAssociationMapper.class, EventMapper.class,
+ AnalysisReportMapper.class, ComponentIndexMapper.class, ComponentLinkMapper.class,
+ Migration45Mapper.class, Migration50Mapper.class
+ };
+ loadMappers(conf, mappers);
+
+ sessionFactory = new SqlSessionFactoryBuilder().build(conf);
+ return this;
+ }
+
+ public SqlSessionFactory getSessionFactory() {
+ return sessionFactory;
+ }
+
+ /**
+ * @deprecated since 4.4. Replaced by <code>openSession(false)</code>.
+ */
+ @Deprecated
+ public SqlSession openSession() {
+ return openSession(false);
+ }
+
+ /**
+ * @deprecated since 4.4. Replaced by <code>openSession(true)</code>.
+ */
+ @Deprecated
+ public BatchSession openBatchSession() {
+ return (BatchSession) openSession(true);
+ }
+
+ /**
+ * @since 4.4
+ */
+ public DbSession openSession(boolean batch) {
+ if (batch) {
+ SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
+ return new BatchSession(queue, session);
+ }
+ SqlSession session = sessionFactory.openSession(ExecutorType.REUSE);
+ return new DbSession(queue, session);
+ }
+
+ private void loadMappers(Configuration mybatisConf, Class<?>... mapperClasses) {
+ for (Class mapperClass : mapperClasses) {
+ loadMapper(mybatisConf, mapperClass);
+ }
+ }
+
+ private void loadMapper(Configuration configuration, Class mapperClass) {
+ loadMapper(configuration, mapperClass.getName());
+ }
+
+ private void loadMapper(Configuration configuration, String mapperName) {
+ InputStream input = null;
+ try {
+ input = getClass().getResourceAsStream("/" + mapperName.replace('.', '/') + ".xml");
+ new XMLMapperBuilder(input, configuration, mapperName, configuration.getSqlFragments()).parse();
+ configuration.addLoadedResource(mapperName);
+ ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(mapperName)).setLevel(Level.INFO);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unable to load mapper " + mapperName, e);
+ } finally {
+ Closeables.closeQuietly(input);
+ }
+ }
+
+ private void loadAlias(Configuration conf, String alias, Class dtoClass) {
+ conf.getTypeAliasRegistry().registerAlias(alias, dtoClass);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/activity/ActivityDto.java b/sonar-db/src/main/java/org/sonar/db/activity/ActivityDto.java
new file mode 100644
index 00000000000..2f0b91aabb1
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/activity/ActivityDto.java
@@ -0,0 +1,104 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.activity;
+
+import java.util.Date;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class ActivityDto {
+
+ private String key;
+ private String message;
+ private String type;
+ private String action;
+ private String author;
+ private String data;
+ private Date createdAt;
+
+ public ActivityDto setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public ActivityDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public ActivityDto setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public ActivityDto setAuthor(@Nullable String author) {
+ this.author = author;
+ return this;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public ActivityDto setData(String data) {
+ this.data = data;
+ return this;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public ActivityDto setMessage(@Nullable String message) {
+ this.message = message;
+ return this;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public ActivityDto setAction(String action) {
+ this.action = action;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/activity/ActivityMapper.java b/sonar-db/src/main/java/org/sonar/db/activity/ActivityMapper.java
new file mode 100644
index 00000000000..467db557e8e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/activity/ActivityMapper.java
@@ -0,0 +1,26 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.activity;
+
+public interface ActivityMapper {
+
+ void insert(ActivityDto dto);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/activity/package-info.java b/sonar-db/src/main/java/org/sonar/db/activity/package-info.java
new file mode 100644
index 00000000000..10134a6fce2
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/activity/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.activity;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentDto.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentDto.java
new file mode 100644
index 00000000000..4e034e4a19b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentDto.java
@@ -0,0 +1,304 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.sonar.api.component.Component;
+import org.sonar.api.resources.Scopes;
+
+public class ComponentDto implements Component {
+
+ public static final String MODULE_UUID_PATH_SEP = ".";
+
+ private Long id;
+ private String uuid;
+ private String kee;
+ private String scope;
+ private String qualifier;
+
+ private String projectUuid;
+ private String moduleUuid;
+ private String moduleUuidPath;
+ private Long parentProjectId;
+ private Long copyResourceId;
+
+ private String path;
+ private String deprecatedKey;
+ private String name;
+ private String longName;
+ private String language;
+ private String description;
+ private boolean enabled = true;
+
+ private Date createdAt;
+ private Long authorizationUpdatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public ComponentDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String uuid() {
+ return uuid;
+ }
+
+ public ComponentDto setUuid(String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+
+ @Override
+ public String key() {
+ return kee;
+ }
+
+ public String scope() {
+ return scope;
+ }
+
+ public ComponentDto setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ @Override
+ public String qualifier() {
+ return qualifier;
+ }
+
+ public ComponentDto setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ @CheckForNull
+ public String deprecatedKey() {
+ return deprecatedKey;
+ }
+
+ public ComponentDto setDeprecatedKey(@Nullable String deprecatedKey) {
+ this.deprecatedKey = deprecatedKey;
+ return this;
+ }
+
+ /**
+ * Return the root project uuid. On a root project, return itself
+ */
+ public String projectUuid() {
+ return projectUuid;
+ }
+
+ public ComponentDto setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ /**
+ * Return the direct module of a component. Will be null on projects
+ */
+ @CheckForNull
+ public String moduleUuid() {
+ return moduleUuid;
+ }
+
+ public ComponentDto setModuleUuid(@Nullable String moduleUuid) {
+ this.moduleUuid = moduleUuid;
+ return this;
+ }
+
+ /**
+ * Return the path from the project to the last modules
+ */
+ public String moduleUuidPath() {
+ return moduleUuidPath;
+ }
+
+ public ComponentDto setModuleUuidPath(String moduleUuidPath) {
+ this.moduleUuidPath = moduleUuidPath;
+ return this;
+ }
+
+ @CheckForNull
+ @Override
+ public String path() {
+ return path;
+ }
+
+ public ComponentDto setPath(@Nullable String path) {
+ this.path = path;
+ return this;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ public ComponentDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public String longName() {
+ return longName;
+ }
+
+ public ComponentDto setLongName(String longName) {
+ this.longName = longName;
+ return this;
+ }
+
+ @CheckForNull
+ public String language() {
+ return language;
+ }
+
+ public ComponentDto setLanguage(@Nullable String language) {
+ this.language = language;
+ return this;
+ }
+
+ @CheckForNull
+ public String description() {
+ return description;
+ }
+
+ public ComponentDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ @CheckForNull
+ public Long parentProjectId() {
+ return parentProjectId;
+ }
+
+ public ComponentDto setParentProjectId(@Nullable Long parentProjectId) {
+ this.parentProjectId = parentProjectId;
+ return this;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public ComponentDto setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ public Long getCopyResourceId() {
+ return copyResourceId;
+ }
+
+ public ComponentDto setCopyResourceId(Long copyResourceId) {
+ this.copyResourceId = copyResourceId;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public ComponentDto setCreatedAt(Date datetime) {
+ this.createdAt = datetime;
+ return this;
+ }
+
+ /**
+ * Only available on projects
+ */
+ @CheckForNull
+ public Long getAuthorizationUpdatedAt() {
+ return authorizationUpdatedAt;
+ }
+
+ public ComponentDto setAuthorizationUpdatedAt(@Nullable Long authorizationUpdatedAt) {
+ this.authorizationUpdatedAt = authorizationUpdatedAt;
+ return this;
+ }
+
+ public String getKey() {
+ return key();
+ }
+
+ public ComponentDto setKey(String key) {
+ this.kee = key;
+ return this;
+ }
+
+ public boolean isRootProject() {
+ return moduleUuid == null && Scopes.PROJECT.equals(scope);
+ }
+
+ // FIXME equals/hashCode mean nothing on DTOs, especially when on id
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ComponentDto that = (ComponentDto) o;
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ return true;
+ }
+
+ // FIXME equals/hashCode mean nothing on DTOs, especially when on id
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("id", id)
+ .append("uuid", uuid)
+ .append("kee", kee)
+ .append("scope", scope)
+ .append("qualifier", qualifier)
+ .append("projectUuid", projectUuid)
+ .append("moduleUuid", moduleUuid)
+ .append("moduleUuidPath", moduleUuidPath)
+ .append("parentProjectId", parentProjectId)
+ .append("copyResourceId", copyResourceId)
+ .append("path", path)
+ .append("deprecatedKey", deprecatedKey)
+ .append("name", name)
+ .append("longName", longName)
+ .append("language", language)
+ .append("enabled", enabled)
+ .append("authorizationUpdatedAt", authorizationUpdatedAt)
+ .toString();
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentIndexMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentIndexMapper.java
new file mode 100644
index 00000000000..44eafac3a24
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentIndexMapper.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface ComponentIndexMapper {
+
+ List<Long> selectProjectIdsFromQueryAndViewOrSubViewUuid(@Param("query") String query, @Param("viewUuidQuery") String viewUuidQuery);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentLinkDto.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentLinkDto.java
new file mode 100644
index 00000000000..62cc435966b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentLinkDto.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+/**
+ * Component links should be merge in a 'links' column (using protobuf for instance) of the projects table.
+ * But to do this we'll have to wait for the measure filters page (where links are displayed) to be rewritten in JS/WS (because it's in Rails for the moment).
+ */
+public class ComponentLinkDto {
+
+ public static final String TYPE_HOME_PAGE = "homepage";
+ public static final String TYPE_CI = "ci";
+ public static final String TYPE_ISSUE_TRACKER = "issue";
+ public static final String TYPE_SOURCES = "scm";
+ public static final String TYPE_SOURCES_DEV = "scm_dev";
+
+ public static final List<String> PROVIDED_TYPES = ImmutableList.of(TYPE_HOME_PAGE, TYPE_CI, TYPE_ISSUE_TRACKER, TYPE_SOURCES, TYPE_SOURCES_DEV);
+
+ private Long id;
+ private String componentUuid;
+ private String type;
+ private String name;
+ private String href;
+
+ public String getName() {
+ return name;
+ }
+
+ public ComponentLinkDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ public ComponentLinkDto setComponentUuid(String componentUuid) {
+ this.componentUuid = componentUuid;
+ return this;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public ComponentLinkDto setHref(String href) {
+ this.href = href;
+ return this;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public ComponentLinkDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public ComponentLinkDto setType(String type) {
+ this.type = type;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentLinkMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentLinkMapper.java
new file mode 100644
index 00000000000..2b7fe818914
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentLinkMapper.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import java.util.List;
+
+public interface ComponentLinkMapper {
+
+ List<ComponentLinkDto> selectByComponentUuid(String componentUuid);
+
+ void insert(ComponentLinkDto dto);
+
+ void update(ComponentLinkDto dto);
+
+ void delete(long id);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
new file mode 100644
index 00000000000..ac38353e9a6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
@@ -0,0 +1,110 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.RowBounds;
+
+/**
+ * @since 4.3
+ */
+public interface ComponentMapper {
+
+ @CheckForNull
+ ComponentDto selectByKey(String key);
+
+ @CheckForNull
+ ComponentDto selectById(long id);
+
+ @CheckForNull
+ ComponentDto selectByUuid(String uuid);
+
+ /**
+ * Return sub project of component keys
+ */
+ List<ComponentDto> selectSubProjectsByComponentUuids(@Param("uuids") Collection<String> uuids);
+
+ List<ComponentDto> selectByKeys(@Param("keys") Collection<String> keys);
+
+ List<ComponentDto> selectByIds(@Param("ids") Collection<Long> ids);
+
+ List<ComponentDto> selectByUuids(@Param("uuids") Collection<String> uuids);
+
+ List<String> selectExistingUuids(@Param("uuids") Collection<String> uuids);
+
+ /**
+ * Return all project (PRJ/TRK) uuids
+ */
+ List<String> selectProjectUuids();
+
+ /**
+ * Return all descendant modules (including itself) from a given component uuid and scope
+ */
+ List<ComponentDto> selectDescendantModules(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,
+ @Param(value = "excludeDisabled") boolean excludeDisabled);
+
+ /**
+ * Return all files from a given project uuid and scope
+ */
+ List<FilePathWithHashDto> selectEnabledFilesFromProject(@Param("projectUuid") String projectUuid);
+
+ /**
+ * Return all descendant files from a given module uuid and scope
+ */
+ List<FilePathWithHashDto> selectDescendantFiles(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,
+ @Param(value = "excludeDisabled") boolean excludeDisabled);
+
+ /**
+ * Return uuids and project uuids from list of qualifiers
+ *
+ * It's using a join on snapshots in order to use he indexed columns snapshots.qualifier
+ */
+ List<UuidWithProjectUuidDto> selectUuidsForQualifiers(@Param("qualifiers") String... qualifiers);
+
+ /**
+ * Return all components of a project
+ */
+ List<ComponentDto> selectComponentsFromProjectKeyAndScope(@Param("projectKey") String projectKey, @Nullable @Param("scope") String scope);
+
+ /**
+ * Return technical projects from a view or a sub-view
+ */
+ List<String> selectProjectsFromView(@Param("viewUuidLikeQuery") String viewUuidLikeQuery, @Param("projectViewUuid") String projectViewUuid);
+
+ long countById(long id);
+
+ List<ComponentDto> selectProvisionedProjects(Map<String, String> parameters, RowBounds rowBounds);
+
+ int countProvisionedProjects(Map<String, String> parameters);
+
+ List<ComponentDto> selectGhostProjects(Map<String, String> parameters, RowBounds rowBounds);
+
+ long countGhostProjects(Map<String, String> parameters);
+
+ void insert(ComponentDto componentDto);
+
+ void update(ComponentDto componentDto);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/FilePathWithHashDto.java b/sonar-db/src/main/java/org/sonar/db/component/FilePathWithHashDto.java
new file mode 100644
index 00000000000..85e3c45ceb6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/FilePathWithHashDto.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+public class FilePathWithHashDto {
+
+ private String uuid;
+ private String moduleUuid;
+ private String path;
+ private String srcHash;
+
+ public String getSrcHash() {
+ return srcHash;
+ }
+
+ public void setSrcHash(String srcHash) {
+ this.srcHash = srcHash;
+ }
+
+ public String getModuleUuid() {
+ return moduleUuid;
+ }
+
+ public void setModuleUuid(String moduleUuid) {
+ this.moduleUuid = moduleUuid;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceDao.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceDao.java
new file mode 100644
index 00000000000..2ebd03ec015
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceDao.java
@@ -0,0 +1,373 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.component.Component;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.Uuids;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class ResourceDao implements Dao {
+ private MyBatis mybatis;
+ private System2 system2;
+
+ public ResourceDao(MyBatis mybatis, System2 system2) {
+ this.mybatis = mybatis;
+ this.system2 = system2;
+ }
+
+ public List<ResourceDto> getResources(ResourceQuery query) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ResourceMapper.class).selectResources(query);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ResourceDto> getResources(ResourceQuery query, SqlSession session) {
+ return session.getMapper(ResourceMapper.class).selectResources(query);
+ }
+
+ /**
+ * Return a single result or null. If the request returns multiple rows, then
+ * the first row is returned.
+ */
+ @CheckForNull
+ public ResourceDto getResource(ResourceQuery query) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getResource(query, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public ResourceDto getResource(ResourceQuery query, DbSession session) {
+ List<ResourceDto> resources = getResources(query, session);
+ if (!resources.isEmpty()) {
+ return resources.get(0);
+ }
+ return null;
+ }
+
+ public List<Long> getResourceIds(ResourceQuery query) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ResourceMapper.class).selectResourceIds(query);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public ResourceDto getResource(long projectId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getResource(projectId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public ResourceDto getResource(String componentUuid) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ResourceMapper.class).selectResourceByUuid(componentUuid);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public ResourceDto getResource(long projectId, SqlSession session) {
+ return session.getMapper(ResourceMapper.class).selectResource(projectId);
+ }
+
+ @CheckForNull
+ public SnapshotDto getLastSnapshot(String resourceKey, SqlSession session) {
+ return session.getMapper(ResourceMapper.class).selectLastSnapshotByResourceKey(resourceKey);
+ }
+
+ @CheckForNull
+ public SnapshotDto getLastSnapshotByResourceUuid(String componentUuid, SqlSession session) {
+ return session.getMapper(ResourceMapper.class).selectLastSnapshotByResourceUuid(componentUuid);
+ }
+
+ public List<ResourceDto> getDescendantProjects(long projectId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getDescendantProjects(projectId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ResourceDto> getDescendantProjects(long projectId, SqlSession session) {
+ ResourceMapper mapper = session.getMapper(ResourceMapper.class);
+ List<ResourceDto> resources = newArrayList();
+ appendChildProjects(projectId, mapper, resources);
+ return resources;
+ }
+
+ private void appendChildProjects(long projectId, ResourceMapper mapper, List<ResourceDto> resources) {
+ List<ResourceDto> subProjects = mapper.selectDescendantProjects(projectId);
+ for (ResourceDto subProject : subProjects) {
+ resources.add(subProject);
+ appendChildProjects(subProject.getId(), mapper, resources);
+ }
+ }
+
+ /**
+ * Used by the Views Plugin
+ */
+ public ResourceDao insertOrUpdate(ResourceDto... resources) {
+ SqlSession session = mybatis.openSession(false);
+ ResourceMapper mapper = session.getMapper(ResourceMapper.class);
+ Date now = new Date(system2.now());
+ try {
+ for (ResourceDto resource : resources) {
+ if (resource.getId() == null) {
+ // Fix for Views
+ if (resource.getUuid() == null && Scopes.PROJECT.equals(resource.getScope())) {
+ String uuid = Uuids.create();
+ resource.setUuid(uuid);
+ resource.setProjectUuid(uuid);
+ resource.setModuleUuidPath("");
+ }
+ resource.setCreatedAt(now);
+ resource.setAuthorizationUpdatedAt(now.getTime());
+ mapper.insert(resource);
+ } else {
+ mapper.update(resource);
+ }
+ }
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ return this;
+ }
+
+ /**
+ * Should not be called from batch side (used to reindex permission in E/S)
+ */
+ public void updateAuthorizationDate(Long projectId, SqlSession session) {
+ session.getMapper(ResourceMapper.class).updateAuthorizationDate(projectId, system2.now());
+ }
+
+ @CheckForNull
+ public Component findByKey(String key) {
+ ResourceDto resourceDto = getResource(ResourceQuery.create().setKey(key));
+ return resourceDto != null ? toComponent(resourceDto) : null;
+ }
+
+ @CheckForNull
+ public Component findById(Long id, SqlSession session) {
+ ResourceDto resourceDto = getResource(id, session);
+ return resourceDto != null ? toComponent(resourceDto) : null;
+ }
+
+ /**
+ * Return the root project of a component.
+ * Will return the component itself if it's already the root project
+ * Can return null if the component does not exists.
+ *
+ * The implementation should rather use a new column already containing the root project, see https://jira.sonarsource.com/browse/SONAR-5188.
+ */
+ @CheckForNull
+ public ResourceDto getRootProjectByComponentKey(DbSession session, String componentKey) {
+ ResourceDto component = getResource(ResourceQuery.create().setKey(componentKey), session);
+ if (component != null) {
+ Long rootId = component.getRootId();
+ if (rootId != null) {
+ return getParentModuleByComponentId(rootId, session);
+ } else {
+ return component;
+ }
+ }
+ return null;
+ }
+
+ @CheckForNull
+ public ResourceDto getRootProjectByComponentKey(String componentKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getRootProjectByComponentKey(session, componentKey);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ ResourceDto getParentModuleByComponentId(Long componentId, DbSession session) {
+ ResourceDto component = getResource(componentId, session);
+ if (component != null) {
+ Long rootId = component.getRootId();
+ if (rootId != null) {
+ return getParentModuleByComponentId(rootId, session);
+ } else {
+ return component;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the root project of a component.
+ * Will return the component itself if it's already the root project
+ * Can return null if the component that does exists.
+ *
+ * The implementation should rather use a new column already containing the root project, see https://jira.sonarsource.com/browse/SONAR-5188.
+ */
+ @CheckForNull
+ public ResourceDto getRootProjectByComponentId(long componentId) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ ResourceDto component = getParentModuleByComponentId(componentId, session);
+ Long rootId = component != null ? component.getRootId() : null;
+ if (rootId != null) {
+ return getParentModuleByComponentId(rootId, session);
+ } else {
+ return component;
+ }
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<Component> selectProjectsByQualifiers(Collection<String> qualifiers) {
+ if (qualifiers.isEmpty()) {
+ return Collections.emptyList();
+ }
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return toComponents(session.getMapper(ResourceMapper.class).selectProjectsByQualifiers(qualifiers));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Return enabled projects including not completed ones, ie without snapshots or without snapshot having islast=true
+ */
+ public List<Component> selectProjectsIncludingNotCompletedOnesByQualifiers(Collection<String> qualifiers) {
+ if (qualifiers.isEmpty()) {
+ return Collections.emptyList();
+ }
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return toComponents(session.getMapper(ResourceMapper.class).selectProjectsIncludingNotCompletedOnesByQualifiers(qualifiers));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Return ghosts projects :
+ * - not enabled projects
+ * - enabled projects without snapshot having islast=true
+ * - enabled projects without snapshot
+ */
+ public List<Component> selectGhostsProjects(Collection<String> qualifiers) {
+ if (qualifiers.isEmpty()) {
+ return Collections.emptyList();
+ }
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return toComponents(session.getMapper(ResourceMapper.class).selectGhostsProjects(qualifiers));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Return provisioned projects = enabled projects without snapshot
+ */
+ public List<ResourceDto> selectProvisionedProjects(Collection<String> qualifiers) {
+ if (qualifiers.isEmpty()) {
+ return Collections.emptyList();
+ }
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ResourceMapper.class).selectProvisionedProjects(qualifiers);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Return provisioned project with given key
+ */
+ public ResourceDto selectProvisionedProject(DbSession session, String key) {
+ return session.getMapper(ResourceMapper.class).selectProvisionedProject(key);
+ }
+
+ public ResourceDto selectProvisionedProject(String key) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return selectProvisionedProject(session, key);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public static ComponentDto toComponent(ResourceDto resourceDto) {
+ return new ComponentDto()
+ .setId(resourceDto.getId())
+ .setKey(resourceDto.getKey())
+ .setPath(resourceDto.getPath())
+ .setLongName(resourceDto.getLongName())
+ .setName(resourceDto.getName())
+ .setQualifier(resourceDto.getQualifier());
+ }
+
+ public static List<Component> toComponents(List<ResourceDto> resourceDto) {
+ return newArrayList(Iterables.transform(resourceDto, ToComponent.INSTANCE));
+ }
+
+ public void insertUsingExistingSession(ResourceDto resourceDto, SqlSession session) {
+ ResourceMapper resourceMapper = session.getMapper(ResourceMapper.class);
+ resourceMapper.insert(resourceDto);
+ }
+
+ private enum ToComponent implements Function<ResourceDto, Component> {
+ INSTANCE;
+
+ @Override
+ public Component apply(@Nonnull ResourceDto resourceDto) {
+ return toComponent(resourceDto);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceDto.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceDto.java
new file mode 100644
index 00000000000..769ee2376fc
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceDto.java
@@ -0,0 +1,226 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import java.util.Date;
+
+public class ResourceDto {
+
+ private Long id;
+ private String uuid;
+ private String projectUuid;
+ private String moduleUuid;
+ private String moduleUuidPath;
+ private String key;
+ private String deprecatedKey;
+ private String name;
+ private String longName;
+ private Long rootId;
+ private String path;
+ private String scope;
+ private String qualifier;
+ private boolean enabled = true;
+ private String description;
+ private String language;
+ private Long copyResourceId;
+ private Long personId;
+ private Date createdAt;
+ private Long authorizationUpdatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public ResourceDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public ResourceDto setUuid(String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public ResourceDto setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ public String getModuleUuid() {
+ return moduleUuid;
+ }
+
+ public ResourceDto setModuleUuid(String moduleUuid) {
+ this.moduleUuid = moduleUuid;
+ return this;
+ }
+
+ public String getModuleUuidPath() {
+ return moduleUuidPath;
+ }
+
+ public ResourceDto setModuleUuidPath(String moduleUuidPath) {
+ this.moduleUuidPath = moduleUuidPath;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ResourceDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public ResourceDto setKey(String s) {
+ this.key = s;
+ return this;
+ }
+
+ public String getDeprecatedKey() {
+ return deprecatedKey;
+ }
+
+ public ResourceDto setDeprecatedKey(String s) {
+ this.deprecatedKey = s;
+ return this;
+ }
+
+ public Long getRootId() {
+ return rootId;
+ }
+
+ public ResourceDto setRootId(Long rootId) {
+ this.rootId = rootId;
+ return this;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public ResourceDto setPath(String s) {
+ this.path = s;
+ return this;
+ }
+
+ public String getLongName() {
+ return longName;
+ }
+
+ public ResourceDto setLongName(String longName) {
+ this.longName = longName;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public ResourceDto setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public ResourceDto setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public ResourceDto setEnabled(boolean b) {
+ this.enabled = b;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public ResourceDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public ResourceDto setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ public Long getCopyResourceId() {
+ return copyResourceId;
+ }
+
+ public ResourceDto setCopyResourceId(Long copyResourceId) {
+ this.copyResourceId = copyResourceId;
+ return this;
+ }
+
+ public Long getPersonId() {
+ return personId;
+ }
+
+ public ResourceDto setPersonId(Long personId) {
+ this.personId = personId;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;// NOSONAR May expose internal representation by returning reference to mutable object
+ }
+
+ public ResourceDto setCreatedAt(Date date) {
+ this.createdAt = date;// NOSONAR May expose internal representation by incorporating reference to mutable object
+ return this;
+ }
+
+ public Long getAuthorizationUpdatedAt() {
+ return authorizationUpdatedAt;
+ }
+
+ public ResourceDto setAuthorizationUpdatedAt(Long authorizationUpdatedAt) {
+ this.authorizationUpdatedAt = authorizationUpdatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexDto.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexDto.java
new file mode 100644
index 00000000000..90ac0944274
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexDto.java
@@ -0,0 +1,93 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+public final class ResourceIndexDto {
+ private Long id;
+ private String key;
+ private int position;
+ private int nameSize;
+ private long resourceId;
+ private long rootProjectId;
+ private String qualifier;
+
+ public Long getId() {
+ return id;
+ }
+
+ public ResourceIndexDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public ResourceIndexDto setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public ResourceIndexDto setPosition(int i) {
+ this.position = i;
+ return this;
+ }
+
+ public long getResourceId() {
+ return resourceId;
+ }
+
+ public ResourceIndexDto setResourceId(long i) {
+ this.resourceId = i;
+ return this;
+ }
+
+ public long getRootProjectId() {
+ return rootProjectId;
+ }
+
+ public ResourceIndexDto setRootProjectId(long i) {
+ this.rootProjectId = i;
+ return this;
+ }
+
+ public int getNameSize() {
+ return nameSize;
+ }
+
+ public ResourceIndexDto setNameSize(int i) {
+ this.nameSize = i;
+ return this;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public ResourceIndexDto setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerDao.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerDao.java
new file mode 100644
index 00000000000..6a61ef714e6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerDao.java
@@ -0,0 +1,216 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class ResourceIndexerDao {
+
+ private static final String SELECT_RESOURCES = "org.sonar.db.component.ResourceIndexerMapper.selectResources";
+ public static final int MINIMUM_KEY_SIZE = 3;
+ public static final int SINGLE_INDEX_SIZE = 2;
+
+ // The scopes and qualifiers that are not in the following constants are not indexed at all.
+ // Directories and packages are explicitly excluded.
+ private static final String[] RENAMABLE_QUALIFIERS = {Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.VIEW, Qualifiers.SUBVIEW};
+ private static final String[] RENAMABLE_SCOPES = {Scopes.PROJECT};
+ private static final String[] NOT_RENAMABLE_QUALIFIERS = {Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE, Qualifiers.CLASS};
+ private static final String[] NOT_RENAMABLE_SCOPES = {Scopes.FILE};
+
+ private final MyBatis mybatis;
+
+ public ResourceIndexerDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ /**
+ * This method is reentrant. It can be executed even if the project is already indexed.
+ */
+ public ResourceIndexerDao indexProject(final long rootProjectId) {
+ DbSession session = mybatis.openSession(true);
+ try {
+ indexProject(rootProjectId, session);
+ session.commit();
+ return this;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void indexProject(final long rootProjectId, DbSession session) {
+ ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
+ doIndexProject(rootProjectId, session, mapper);
+ }
+
+ /**
+ * This method is reentrant. It can be executed even if some projects are already indexed.
+ */
+ public ResourceIndexerDao indexProjects() {
+ final DbSession session = mybatis.openSession(true);
+ try {
+ final ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
+ session.select("org.sonar.db.component.ResourceIndexerMapper.selectRootProjectIds", /* workaround to get booleans */ResourceIndexerQuery.create(), new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext context) {
+ Integer rootProjectId = (Integer) context.getResultObject();
+ doIndexProject(rootProjectId, session, mapper);
+ session.commit();
+ }
+ });
+ return this;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private void doIndexProject(long rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) {
+ // non indexed resources
+ ResourceIndexerQuery query = ResourceIndexerQuery.create()
+ .setNonIndexedOnly(true)
+ .setQualifiers(NOT_RENAMABLE_QUALIFIERS)
+ .setScopes(NOT_RENAMABLE_SCOPES)
+ .setRootProjectId(rootProjectId);
+
+ session.select(SELECT_RESOURCES, query, new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext context) {
+ ResourceDto resource = (ResourceDto) context.getResultObject();
+ doIndex(resource, mapper);
+ }
+ });
+
+ // some resources can be renamed, so index must be regenerated
+ // -> delete existing rows and create them again
+ query = ResourceIndexerQuery.create()
+ .setNonIndexedOnly(false)
+ .setQualifiers(RENAMABLE_QUALIFIERS)
+ .setScopes(RENAMABLE_SCOPES)
+ .setRootProjectId(rootProjectId);
+
+ session.select(SELECT_RESOURCES, query, new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext context) {
+ ResourceDto resource = (ResourceDto) context.getResultObject();
+
+ mapper.deleteByResourceId(resource.getId());
+ doIndex(resource, mapper);
+ }
+ });
+ }
+
+ void doIndex(ResourceDto resource, ResourceIndexerMapper mapper) {
+ String key = nameToKey(resource.getName());
+ if (key.length() >= MINIMUM_KEY_SIZE || key.length() == SINGLE_INDEX_SIZE) {
+ insertIndexEntries(key, resource.getId(), resource.getQualifier(), resource.getRootId(), resource.getName().length(), mapper);
+ }
+ }
+
+ public boolean indexResource(long id) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return indexResource(session, id);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public boolean indexResource(DbSession session, long id) {
+ boolean indexed = false;
+ ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
+ ResourceDto resource = mapper.selectResourceToIndex(id);
+ if (resource != null) {
+ Long rootId = resource.getRootId();
+ if (rootId == null) {
+ rootId = resource.getId();
+ }
+ indexed = indexResource(resource.getId(), resource.getName(), resource.getQualifier(), rootId, session, mapper);
+ }
+ return indexed;
+ }
+
+ public boolean indexResource(int id, String name, String qualifier, int rootId) {
+ boolean indexed = false;
+ SqlSession session = mybatis.openSession(false);
+ ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
+ try {
+ indexed = indexResource(id, name, qualifier, rootId, session, mapper);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ return indexed;
+ }
+
+ private boolean indexResource(long id, String name, String qualifier, long rootId, SqlSession session, ResourceIndexerMapper mapper) {
+ boolean indexed = false;
+ String key = nameToKey(name);
+ if (key.length() >= MINIMUM_KEY_SIZE || key.length() == SINGLE_INDEX_SIZE) {
+ indexed = true;
+ boolean toBeIndexed = sanitizeIndex(id, key, mapper);
+ if (toBeIndexed) {
+ insertIndexEntries(key, id, qualifier, rootId, name.length(), mapper);
+ session.commit();
+ }
+ }
+ return indexed;
+ }
+
+ private void insertIndexEntries(String key, long resourceId, String qualifier, long rootId, int nameLength, ResourceIndexerMapper mapper) {
+ ResourceIndexDto dto = new ResourceIndexDto()
+ .setResourceId(resourceId)
+ .setQualifier(qualifier)
+ .setRootProjectId(rootId)
+ .setNameSize(nameLength);
+
+ int maxPosition = key.length() == SINGLE_INDEX_SIZE ? 0 : key.length() - MINIMUM_KEY_SIZE;
+ for (int position = 0; position <= maxPosition; position++) {
+ dto.setPosition(position);
+ dto.setKey(StringUtils.substring(key, position));
+ mapper.insert(dto);
+ }
+ }
+
+ /**
+ * Return true if the resource must be indexed, false if the resource is already indexed.
+ * If the resource is indexed with a different key, then this index is dropped and the
+ * resource must be indexed again.
+ */
+ private boolean sanitizeIndex(long resourceId, String key, ResourceIndexerMapper mapper) {
+ ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resourceId);
+ if (masterIndex != null && !StringUtils.equals(key, masterIndex.getKey())) {
+ // resource has been renamed -> drop existing indexes
+ mapper.deleteByResourceId(resourceId);
+ masterIndex = null;
+ }
+ return masterIndex == null;
+ }
+
+ static String nameToKey(String input) {
+ return StringUtils.lowerCase(StringUtils.trimToEmpty(input));
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerMapper.java
new file mode 100644
index 00000000000..ecbacc7da82
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerMapper.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+public interface ResourceIndexerMapper {
+
+ ResourceIndexDto selectMasterIndexByResourceId(long resourceId);
+
+ ResourceDto selectResourceToIndex(long resourceId);
+
+ void deleteByResourceId(long resourceId);
+
+ void insert(ResourceIndexDto dto);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerQuery.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerQuery.java
new file mode 100644
index 00000000000..f05204aa130
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceIndexerQuery.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+final class ResourceIndexerQuery {
+ private long rootProjectId;
+ private String[] scopes = null;
+ private String[] qualifiers = null;
+ private boolean nonIndexedOnly = false;
+
+ private ResourceIndexerQuery() {
+ }
+
+ public static ResourceIndexerQuery create() {
+ return new ResourceIndexerQuery();
+ }
+
+ public String[] getScopes() {
+ return scopes;
+ }
+
+ public String[] getQualifiers() {
+ return qualifiers;
+ }
+
+ public ResourceIndexerQuery setScopes(String[] scopes) {
+ this.scopes = scopes;
+ return this;
+ }
+
+ public ResourceIndexerQuery setQualifiers(String[] qualifiers) {
+ this.qualifiers = qualifiers;
+ return this;
+ }
+
+ public long getRootProjectId() {
+ return rootProjectId;
+ }
+
+ public ResourceIndexerQuery setRootProjectId(long i) {
+ this.rootProjectId = i;
+ return this;
+ }
+
+ public boolean isNonIndexedOnly() {
+ return nonIndexedOnly;
+ }
+
+ public ResourceIndexerQuery setNonIndexedOnly(boolean b) {
+ this.nonIndexedOnly = b;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterDao.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterDao.java
new file mode 100644
index 00000000000..6441c88df37
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterDao.java
@@ -0,0 +1,156 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang.StringUtils;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+/**
+ * Class used to rename the key of a project and its resources.
+ *
+ * @since 3.2
+ */
+public class ResourceKeyUpdaterDao {
+ private MyBatis mybatis;
+
+ public ResourceKeyUpdaterDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public void updateKey(long projectId, String newKey) {
+ DbSession session = mybatis.openSession(true);
+ ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class);
+ try {
+ if (mapper.countResourceByKey(newKey) > 0) {
+ throw new IllegalStateException("Impossible to update key: a resource with \"" + newKey + "\" key already exists.");
+ }
+
+ // must SELECT first everything
+ ResourceDto project = mapper.selectProject(projectId);
+ String projectOldKey = project.getKey();
+ List<ResourceDto> resources = mapper.selectProjectResources(projectId);
+ resources.add(project);
+
+ // and then proceed with the batch UPDATE at once
+ runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper);
+
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Map<String, String> checkModuleKeysBeforeRenaming(long projectId, String stringToReplace, String replacementString) {
+ SqlSession session = mybatis.openSession(false);
+ ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class);
+ Map<String, String> result = Maps.newHashMap();
+ try {
+ Set<ResourceDto> modules = collectAllModules(projectId, stringToReplace, mapper);
+ for (ResourceDto module : modules) {
+ String newKey = computeNewKey(module, stringToReplace, replacementString);
+ if (mapper.countResourceByKey(newKey) > 0) {
+ result.put(module.getKey(), "#duplicate_key#");
+ } else {
+ result.put(module.getKey(), newKey);
+ }
+ }
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ return result;
+ }
+
+ public void bulkUpdateKey(DbSession session, long projectId, String stringToReplace, String replacementString) {
+ ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class);
+ // must SELECT first everything
+ Set<ResourceDto> modules = collectAllModules(projectId, stringToReplace, mapper);
+ checkNewNameOfAllModules(modules, stringToReplace, replacementString, mapper);
+ Map<ResourceDto, List<ResourceDto>> allResourcesByModuleMap = Maps.newHashMap();
+ for (ResourceDto module : modules) {
+ allResourcesByModuleMap.put(module, mapper.selectProjectResources(module.getId()));
+ }
+
+ // and then proceed with the batch UPDATE at once
+ for (ResourceDto module : modules) {
+ String oldModuleKey = module.getKey();
+ String newModuleKey = computeNewKey(module, stringToReplace, replacementString);
+ Collection<ResourceDto> resources = Lists.newArrayList(module);
+ resources.addAll(allResourcesByModuleMap.get(module));
+ runBatchUpdateForAllResources(resources, oldModuleKey, newModuleKey, mapper);
+ }
+ }
+
+ public void bulkUpdateKey(long projectId, String stringToReplace, String replacementString) {
+ DbSession session = mybatis.openSession(true);
+ try {
+ bulkUpdateKey(session, projectId, stringToReplace, replacementString);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private static String computeNewKey(ResourceDto resource, String stringToReplace, String replacementString) {
+ return resource.getKey().replaceAll(stringToReplace, replacementString);
+ }
+
+ private static void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ResourceKeyUpdaterMapper mapper) {
+ for (ResourceDto resource : resources) {
+ String resourceKey = resource.getKey();
+ resource.setKey(newKey + resourceKey.substring(oldKey.length(), resourceKey.length()));
+ String resourceDeprecatedKey = resource.getDeprecatedKey();
+ if (StringUtils.isNotBlank(resourceDeprecatedKey)) {
+ resource.setDeprecatedKey(newKey + resourceDeprecatedKey.substring(oldKey.length(), resourceDeprecatedKey.length()));
+ }
+ mapper.update(resource);
+ }
+ }
+
+ private Set<ResourceDto> collectAllModules(long projectId, String stringToReplace, ResourceKeyUpdaterMapper mapper) {
+ ResourceDto project = mapper.selectProject(projectId);
+ Set<ResourceDto> modules = Sets.newHashSet();
+ if (project.getKey().contains(stringToReplace)) {
+ modules.add(project);
+ }
+ for (ResourceDto submodule : mapper.selectDescendantProjects(projectId)) {
+ modules.addAll(collectAllModules(submodule.getId(), stringToReplace, mapper));
+ }
+ return modules;
+ }
+
+ private void checkNewNameOfAllModules(Set<ResourceDto> modules, String stringToReplace, String replacementString, ResourceKeyUpdaterMapper mapper) {
+ for (ResourceDto module : modules) {
+ String newName = computeNewKey(module, stringToReplace, replacementString);
+ if (mapper.countResourceByKey(newName) > 0) {
+ throw new IllegalStateException("Impossible to update key: a resource with \"" + newName + "\" key already exists.");
+ }
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterMapper.java
new file mode 100644
index 00000000000..de5b61e2af0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceKeyUpdaterMapper.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import java.util.List;
+
+/**
+ * @since 3.2
+ */
+public interface ResourceKeyUpdaterMapper {
+
+ int countResourceByKey(String key);
+
+ ResourceDto selectProject(long projectId);
+
+ List<ResourceDto> selectProjectResources(long projectId);
+
+ List<ResourceDto> selectDescendantProjects(long projectId);
+
+ void update(ResourceDto resource);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceMapper.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceMapper.java
new file mode 100644
index 00000000000..885527065fb
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceMapper.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.ResultHandler;
+
+public interface ResourceMapper {
+ SnapshotDto selectSnapshot(Long snapshotId);
+
+ SnapshotDto selectLastSnapshotByResourceKey(String resourceKey);
+
+ SnapshotDto selectLastSnapshotByResourceUuid(String componentUuid);
+
+ ResourceDto selectResource(long id);
+
+ ResourceDto selectResourceByUuid(String uuid);
+
+ List<ResourceDto> selectDescendantProjects(long rootProjectId);
+
+ /**
+ * @since 3.0
+ */
+ List<ResourceDto> selectResources(ResourceQuery query);
+
+ /**
+ * @since 3.0
+ */
+ List<Long> selectResourceIds(ResourceQuery query);
+
+ /**
+ * @since 3.2
+ */
+ void selectResources(ResourceQuery query, ResultHandler resultHandler);
+
+ /**
+ * @since 3.6
+ */
+ ResourceDto selectRootProjectByComponentKey(@Param("componentKey") String componentKey);
+
+ /**
+ * @since 3.6
+ */
+ ResourceDto selectRootProjectByComponentId(@Param("componentId") long componentId);
+
+ List<ResourceDto> selectProjectsIncludingNotCompletedOnesByQualifiers(@Param("qualifiers") Collection<String> qualifier);
+
+ List<ResourceDto> selectProjectsByQualifiers(@Param("qualifiers") Collection<String> qualifier);
+
+ List<ResourceDto> selectGhostsProjects(@Param("qualifiers") Collection<String> qualifier);
+
+ List<ResourceDto> selectProvisionedProjects(@Param("qualifiers") Collection<String> qualifier);
+
+ ResourceDto selectProvisionedProject(@Param("key") String key);
+
+ void insert(ResourceDto resource);
+
+ void update(ResourceDto resource);
+
+ void updateAuthorizationDate(@Param("projectId") Long projectId, @Param("authorizationDate") Long authorizationDate);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/ResourceQuery.java b/sonar-db/src/main/java/org/sonar/db/component/ResourceQuery.java
new file mode 100644
index 00000000000..898c5b0b528
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/ResourceQuery.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+/**
+ * @since 3.0
+ */
+public class ResourceQuery {
+ private String[] qualifiers = null;
+ private String key = null;
+ private boolean excludeDisabled = false;
+
+ private ResourceQuery() {
+ }
+
+ public static ResourceQuery create() {
+ return new ResourceQuery();
+ }
+
+ public String[] getQualifiers() {
+ return qualifiers;
+ }
+
+ public ResourceQuery setQualifiers(String[] qualifiers) {
+ this.qualifiers = qualifiers;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public ResourceQuery setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public boolean isExcludeDisabled() {
+ return excludeDisabled;
+ }
+
+ public ResourceQuery setExcludeDisabled(boolean b) {
+ this.excludeDisabled = b;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotDto.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDto.java
new file mode 100644
index 00000000000..c421ce1b51f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDto.java
@@ -0,0 +1,345 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public final class SnapshotDto {
+
+ /**
+ * This status is set on the snapshot at the beginning of the batch
+ */
+ public static final String STATUS_UNPROCESSED = "U";
+ public static final String STATUS_PROCESSED = "P";
+
+ private static final String INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5 = "Index should be in range from 1 to 5";
+
+ private Long id;
+ private Long parentId;
+ private Long rootId;
+ private Long rootProjectId;
+
+ private Long createdAt;
+ private Long buildDate;
+ private Long componentId;
+ private String status = STATUS_UNPROCESSED;
+ private Integer purgeStatus;
+ private Boolean last;
+ private String scope;
+ private String qualifier;
+ private String version;
+ private String path;
+ private Integer depth;
+
+ private String period1Mode;
+ private String period2Mode;
+ private String period3Mode;
+ private String period4Mode;
+ private String period5Mode;
+
+ private String period1Param;
+ private String period2Param;
+ private String period3Param;
+ private String period4Param;
+ private String period5Param;
+
+ private Long period1Date;
+ private Long period2Date;
+ private Long period3Date;
+ private Long period4Date;
+ private Long period5Date;
+
+ public Long getId() {
+ return id;
+ }
+
+ public SnapshotDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getParentId() {
+ return parentId;
+ }
+
+ public SnapshotDto setParentId(@Nullable Long parentId) {
+ this.parentId = parentId;
+ return this;
+ }
+
+ /**
+ * Root id is null on project's snapshot
+ */
+ @CheckForNull
+ public Long getRootId() {
+ return rootId;
+ }
+
+ public SnapshotDto setRootId(@Nullable Long rootId) {
+ this.rootId = rootId;
+ return this;
+ }
+
+ public Long getBuildDate() {
+ return buildDate;
+ }
+
+ public SnapshotDto setBuildDate(Long buildDate) {
+ this.buildDate = buildDate;
+ return this;
+ }
+
+ public Long getComponentId() {
+ return componentId;
+ }
+
+ public SnapshotDto setComponentId(Long componentId) {
+ this.componentId = componentId;
+ return this;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public SnapshotDto setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getPurgeStatus() {
+ return purgeStatus;
+ }
+
+ public SnapshotDto setPurgeStatus(@Nullable Integer purgeStatus) {
+ this.purgeStatus = purgeStatus;
+ return this;
+ }
+
+ public Boolean getLast() {
+ return last;
+ }
+
+ public SnapshotDto setLast(Boolean last) {
+ this.last = last;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public SnapshotDto setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public SnapshotDto setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ /**
+ * Version is only available on projects and modules
+ */
+ @CheckForNull
+ public String getVersion() {
+ return version;
+ }
+
+ public SnapshotDto setVersion(@Nullable String version) {
+ this.version = version;
+ return this;
+ }
+
+ /**
+ * On project's snapshot, the path is empty (or null on Oracle)
+ */
+ @CheckForNull
+ public String getPath() {
+ return path;
+ }
+
+ public SnapshotDto setPath(@Nullable String path) {
+ this.path = path;
+ return this;
+ }
+
+ public Integer getDepth() {
+ return depth;
+ }
+
+ public SnapshotDto setDepth(Integer depth) {
+ this.depth = depth;
+ return this;
+ }
+
+ public Long getRootProjectId() {
+ return rootProjectId;
+ }
+
+ public SnapshotDto setRootProjectId(Long rootProjectId) {
+ this.rootProjectId = rootProjectId;
+ return this;
+ }
+
+ public SnapshotDto setPeriodMode(int index, @Nullable String p) {
+ switch (index) {
+ case 1:
+ period1Mode = p;
+ break;
+ case 2:
+ period2Mode = p;
+ break;
+ case 3:
+ period3Mode = p;
+ break;
+ case 4:
+ period4Mode = p;
+ break;
+ case 5:
+ period5Mode = p;
+ break;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriodMode(int index) {
+ switch (index) {
+ case 1:
+ return period1Mode;
+ case 2:
+ return period2Mode;
+ case 3:
+ return period3Mode;
+ case 4:
+ return period4Mode;
+ case 5:
+ return period5Mode;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ }
+
+ public SnapshotDto setPeriodParam(int index, @Nullable String p) {
+ switch (index) {
+ case 1:
+ period1Param = p;
+ break;
+ case 2:
+ period2Param = p;
+ break;
+ case 3:
+ period3Param = p;
+ break;
+ case 4:
+ period4Param = p;
+ break;
+ case 5:
+ period5Param = p;
+ break;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriodModeParameter(int periodIndex) {
+ switch (periodIndex) {
+ case 1:
+ return period1Param;
+ case 2:
+ return period2Param;
+ case 3:
+ return period3Param;
+ case 4:
+ return period4Param;
+ case 5:
+ return period5Param;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ }
+
+ public SnapshotDto setPeriodDate(int index, @Nullable Long date) {
+ switch (index) {
+ case 1:
+ period1Date = date;
+ break;
+ case 2:
+ period2Date = date;
+ break;
+ case 3:
+ period3Date = date;
+ break;
+ case 4:
+ period4Date = date;
+ break;
+ case 5:
+ period5Date = date;
+ break;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ return this;
+ }
+
+ @CheckForNull
+ public Long getPeriodDate(int periodIndex) {
+ switch (periodIndex) {
+ case 1:
+ return period1Date;
+ case 2:
+ return period2Date;
+ case 3:
+ return period3Date;
+ case 4:
+ return period4Date;
+ case 5:
+ return period5Date;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ }
+
+ public SnapshotDto setCreatedAt(Long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ public Long getRootIdOrSelf() {
+ return getRootId() == null ? getId() : getRootId();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java
new file mode 100644
index 00000000000..d9f29d97d0c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface SnapshotMapper {
+
+ @CheckForNull
+ SnapshotDto selectByKey(long id);
+
+ void insert(SnapshotDto snapshot);
+
+ @CheckForNull
+ SnapshotDto selectLastSnapshot(Long resourceId);
+
+ List<SnapshotDto> selectSnapshotsByQuery(@Param("query") SnapshotQuery query);
+
+ List<SnapshotDto> selectPreviousVersionSnapshots(@Param(value = "componentId") Long componentId, @Param(value = "lastVersion") String lastVersion);
+
+ List<SnapshotDto> selectSnapshotAndChildrenOfScope(@Param(value = "snapshot") Long resourceId, @Param(value = "scope") String scope);
+
+ int updateSnapshotAndChildrenLastFlagAndStatus(@Param(value = "root") Long rootId, @Param(value = "pathRootId") Long pathRootId,
+ @Param(value = "path") String path, @Param(value = "isLast") boolean isLast, @Param(value = "status") String status);
+
+ int updateSnapshotAndChildrenLastFlag(@Param(value = "root") Long rootId, @Param(value = "pathRootId") Long pathRootId,
+ @Param(value = "path") String path, @Param(value = "isLast") boolean isLast);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotQuery.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotQuery.java
new file mode 100644
index 00000000000..fd03cfeb9de
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotQuery.java
@@ -0,0 +1,136 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public final class SnapshotQuery {
+
+ public enum SORT_FIELD {
+ BY_DATE("created_at");
+ final String fieldName;
+
+ SORT_FIELD(String fieldName) {
+ this.fieldName = fieldName;
+ }
+ }
+
+ public enum SORT_ORDER {
+ ASC("asc"), DESC("desc");
+ final String order;
+
+ SORT_ORDER(String order) {
+ this.order = order;
+ }
+ }
+
+ private Long componentId;
+ private Long createdAfter;
+ private Long createdBefore;
+ private String status;
+ private String version;
+ private Boolean isLast;
+ private String sortField;
+ private String sortOrder;
+
+ /**
+ * filter to return snapshots created at or after a given date
+ */
+ @CheckForNull
+ public Long getCreatedAfter() {
+ return createdAfter;
+ }
+
+ public SnapshotQuery setCreatedAfter(@Nullable Long createdAfter) {
+ this.createdAfter = createdAfter;
+ return this;
+ }
+
+ /**
+ * filter to return snapshots created before a given date
+ */
+ @CheckForNull
+ public Long getCreatedBefore() {
+ return createdBefore;
+ }
+
+ public SnapshotQuery setCreatedBefore(@Nullable Long createdBefore) {
+ this.createdBefore = createdBefore;
+ return this;
+ }
+
+ @CheckForNull
+ public Boolean getIsLast() {
+ return isLast;
+ }
+
+ public SnapshotQuery setIsLast(@Nullable Boolean isLast) {
+ this.isLast = isLast;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getComponentId() {
+ return componentId;
+ }
+
+ public SnapshotQuery setComponentId(@Nullable Long componentId) {
+ this.componentId = componentId;
+ return this;
+ }
+
+ @CheckForNull
+ public String getStatus() {
+ return status;
+ }
+
+ public SnapshotQuery setStatus(@Nullable String status) {
+ this.status = status;
+ return this;
+ }
+
+ @CheckForNull
+ public String getVersion() {
+ return version;
+ }
+
+ public SnapshotQuery setVersion(@Nullable String version) {
+ this.version = version;
+ return this;
+ }
+
+ public SnapshotQuery setSort(SORT_FIELD sortField, SORT_ORDER sortOrder) {
+ this.sortField = sortField.fieldName;
+ this.sortOrder = sortOrder.order;
+ return this;
+ }
+
+ @CheckForNull
+ public String getSortField() {
+ return sortField;
+ }
+
+ @CheckForNull
+ public String getSortOrder() {
+ return sortOrder;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/UuidWithProjectUuidDto.java b/sonar-db/src/main/java/org/sonar/db/component/UuidWithProjectUuidDto.java
new file mode 100644
index 00000000000..d8667623b01
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/UuidWithProjectUuidDto.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+public class UuidWithProjectUuidDto {
+
+ private String uuid;
+ private String projectUuid;
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public UuidWithProjectUuidDto setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public UuidWithProjectUuidDto setUuid(String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/component/package-info.java b/sonar-db/src/main/java/org/sonar/db/component/package-info.java
new file mode 100644
index 00000000000..ccaf374bc27
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/component/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.component;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportDto.java b/sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportDto.java
new file mode 100644
index 00000000000..010afe179e7
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportDto.java
@@ -0,0 +1,143 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.compute;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import javax.annotation.CheckForNull;
+
+public class AnalysisReportDto {
+ private Long id;
+ private String projectKey;
+ private String projectName;
+ private Status status;
+ private String uuid;
+ private Long createdAt;
+ private Long updatedAt;
+ private Long startedAt;
+ private Long finishedAt;
+
+ @VisibleForTesting
+ public static AnalysisReportDto newForTests(Long id) {
+ AnalysisReportDto report = new AnalysisReportDto();
+ report.id = id;
+
+ return report;
+ }
+
+ public String getProjectKey() {
+ return projectKey;
+ }
+
+ public AnalysisReportDto setProjectKey(String projectKey) {
+ this.projectKey = projectKey;
+ return this;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public AnalysisReportDto setProjectName(String projectName) {
+ this.projectName = projectName;
+ return this;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public AnalysisReportDto setStatus(Status status) {
+ this.status = status;
+ return this;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public AnalysisReportDto setUuid(String s) {
+ this.uuid = s;
+ return this;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("id", getId())
+ .add("projectKey", getProjectKey())
+ .add("uuid", getUuid())
+ .add("status", getStatus())
+ .add("createdAt", getCreatedAt())
+ .add("startedAt", getStartedAt())
+ .add("finishedAt", getFinishedAt())
+ .toString();
+ }
+
+ @CheckForNull
+ public Long getStartedAt() {
+ return startedAt;
+ }
+
+ public AnalysisReportDto setStartedAt(Long startedAt) {
+ this.startedAt = startedAt;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getFinishedAt() {
+ return finishedAt;
+ }
+
+ public AnalysisReportDto setFinishedAt(Long finishedAt) {
+ this.finishedAt = finishedAt;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ public AnalysisReportDto setCreatedAt(Long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public AnalysisReportDto setUpdatedAt(Long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public enum Status {
+ PENDING, WORKING, SUCCESS, FAILED, CANCELLED
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportMapper.java b/sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportMapper.java
new file mode 100644
index 00000000000..daa9e465a89
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/compute/AnalysisReportMapper.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.compute;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface AnalysisReportMapper {
+ List<AnalysisReportDto> selectByProjectKey(String projectKey);
+
+ List<Long> selectAvailables(
+ @Param("availableStatus") AnalysisReportDto.Status availableStatus,
+ @Param("busyStatus") AnalysisReportDto.Status busyStatus);
+
+ void resetAllToPendingStatus(@Param("updatedAt") long updatedAt);
+
+ void truncate();
+
+ void insert(AnalysisReportDto reportDto);
+
+ int update(AnalysisReportDto report);
+
+ int updateWithBookingReport(@Param("id") Long id, @Param("startedAt") long startedAt,
+ @Param("availableStatus") AnalysisReportDto.Status availableStatus,
+ @Param("busyStatus") AnalysisReportDto.Status busyStatus);
+
+ AnalysisReportDto selectById(long id);
+
+ void delete(long id);
+
+ List<AnalysisReportDto> selectAll();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/compute/package-info.java b/sonar-db/src/main/java/org/sonar/db/compute/package-info.java
new file mode 100644
index 00000000000..240f322233d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/compute/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.compute;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDao.java b/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDao.java
new file mode 100644
index 00000000000..8463da58cfa
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDao.java
@@ -0,0 +1,84 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.Dao;
+import org.sonar.db.MyBatis;
+
+@ServerSide
+public class ActiveDashboardDao implements Dao {
+
+ private MyBatis mybatis;
+
+ public ActiveDashboardDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public void insert(ActiveDashboardDto activeDashboardDto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).insert(activeDashboardDto);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public int selectMaxOrderIndexForNullUser() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ Integer max = getMapper(session).selectMaxOrderIndexForNullUser();
+ return max != null ? max.intValue() : 0;
+ } finally {
+ session.close();
+ }
+
+ }
+
+ public List<DashboardDto> selectGlobalDashboardsForUserLogin(@Nullable String login) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectGlobalDashboardsForUserLogin(login);
+ } finally {
+ session.close();
+ }
+ }
+
+ public List<DashboardDto> selectProjectDashboardsForUserLogin(@Nullable String login) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectProjectDashboardsForUserLogin(session, login);
+ } finally {
+ session.close();
+ }
+ }
+
+ public List<DashboardDto> selectProjectDashboardsForUserLogin(SqlSession session, @Nullable String login) {
+ return getMapper(session).selectProjectDashboardsForUserLogin(login);
+ }
+
+ private ActiveDashboardMapper getMapper(SqlSession session) {
+ return session.getMapper(ActiveDashboardMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDto.java b/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDto.java
new file mode 100644
index 00000000000..7bdb6fcaa58
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardDto.java
@@ -0,0 +1,106 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import com.google.common.base.Objects;
+
+public final class ActiveDashboardDto {
+ private Long id;
+ private Long dashboardId;
+ private Long userId;
+ private Integer orderIndex;
+
+ /**
+ * @return the id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public ActiveDashboardDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * @return the dashboardId
+ */
+ public Long getDashboardId() {
+ return dashboardId;
+ }
+
+ /**
+ * @param dashboardId the dashboardId to set
+ */
+ public ActiveDashboardDto setDashboardId(Long dashboardId) {
+ this.dashboardId = dashboardId;
+ return this;
+ }
+
+ /**
+ * @return the userId
+ */
+ public Long getUserId() {
+ return userId;
+ }
+
+ /**
+ * @param userId the userId to set
+ */
+ public ActiveDashboardDto setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ /**
+ * @return the orderIndex
+ */
+ public Integer getOrderIndex() {
+ return orderIndex;
+ }
+
+ /**
+ * @param orderIndex the orderIndex to set
+ */
+ public ActiveDashboardDto setOrderIndex(Integer orderIndex) {
+ this.orderIndex = orderIndex;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ActiveDashboardDto that = (ActiveDashboardDto) o;
+ return !(id != null ? !id.equals(that.id) : that.id != null);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardMapper.java b/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardMapper.java
new file mode 100644
index 00000000000..83c1ef4e08b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/ActiveDashboardMapper.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+public interface ActiveDashboardMapper {
+
+ void insert(ActiveDashboardDto activeDashboardDto);
+
+ @CheckForNull
+ Integer selectMaxOrderIndexForNullUser();
+
+ List<DashboardDto> selectGlobalDashboardsForUserLogin(@Nullable @Param("login") String login);
+
+ List<DashboardDto> selectProjectDashboardsForUserLogin(@Nullable @Param("login") String login);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDao.java b/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDao.java
new file mode 100644
index 00000000000..40b151e7ec9
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDao.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.MyBatis;
+
+public class DashboardDao implements Dao {
+
+ private MyBatis mybatis;
+
+ public DashboardDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public DashboardDto selectGlobalDashboard(String name) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ DashboardMapper mapper = session.getMapper(DashboardMapper.class);
+ return mapper.selectGlobalDashboard(name);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(DashboardDto dashboardDto) {
+ SqlSession session = mybatis.openSession(false);
+ DashboardMapper dashboardMapper = session.getMapper(DashboardMapper.class);
+ WidgetMapper widgetMapper = session.getMapper(WidgetMapper.class);
+ WidgetPropertyMapper widgetPropertyMapper = session.getMapper(WidgetPropertyMapper.class);
+ try {
+ dashboardMapper.insert(dashboardDto);
+ for (WidgetDto widgetDto : dashboardDto.getWidgets()) {
+ widgetDto.setDashboardId(dashboardDto.getId());
+ widgetMapper.insert(widgetDto);
+ for (WidgetPropertyDto widgetPropertyDto : widgetDto.getWidgetProperties()) {
+ widgetPropertyDto.setWidgetId(widgetDto.getId());
+ widgetPropertyMapper.insert(widgetPropertyDto);
+ }
+ }
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDto.java b/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDto.java
new file mode 100644
index 00000000000..4b56a8937b6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardDto.java
@@ -0,0 +1,115 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.List;
+import org.sonar.db.Dto;
+
+public final class DashboardDto extends Dto<Long> {
+
+ private Long id;
+ private Long userId;
+ private String name;
+ private String description;
+ private String columnLayout;
+ private boolean shared;
+ private boolean global;
+ private List<WidgetDto> widgetDtos = Lists.newArrayList();
+
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public Long getKey() {
+ return id;
+ }
+
+ public DashboardDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public DashboardDto setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public DashboardDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public DashboardDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getColumnLayout() {
+ return columnLayout;
+ }
+
+ public DashboardDto setColumnLayout(String columnLayout) {
+ this.columnLayout = columnLayout;
+ return this;
+ }
+
+ public boolean getShared() {
+ return shared;
+ }
+
+ public DashboardDto setShared(boolean shared) {
+ this.shared = shared;
+ return this;
+ }
+
+ public boolean getGlobal() {
+ return global;
+ }
+
+ public DashboardDto setGlobal(boolean global) {
+ this.global = global;
+ return this;
+ }
+
+ public Collection<WidgetDto> getWidgets() {
+ return widgetDtos;
+ }
+
+ public DashboardDto addWidget(WidgetDto widgetDto) {
+ widgetDtos.add(widgetDto);
+ return this;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardMapper.java b/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardMapper.java
new file mode 100644
index 00000000000..a6222c6fcb8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/DashboardMapper.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+public interface DashboardMapper {
+
+ String COLUMNS = "id, user_id as \"userId\", name, description, column_layout as \"columnLayout\", " +
+ "shared, is_global as \"global\", created_at as \"createdAt\", updated_at as \"updatedAt\"";
+
+ @CheckForNull
+ @Select("select " + COLUMNS + " from dashboards where id=#{id}")
+ DashboardDto selectById(long id);
+
+ @CheckForNull
+ @Select("select " + COLUMNS + " from dashboards where id=#{id} and (shared=${_true} or user_id=${userId})")
+ DashboardDto selectAllowedById(@Param("id") long id, @Param("userId") long userId);
+
+ @CheckForNull
+ @Select("select " + COLUMNS + " from dashboards WHERE name=#{id} and user_id is null")
+ DashboardDto selectGlobalDashboard(String name);
+
+ @Insert("INSERT INTO dashboards (user_id, name, description, column_layout, shared, is_global, created_at, " +
+ "updated_at) VALUES (#{userId}, #{name}, #{description}, #{columnLayout}, #{shared}, " +
+ "#{global}, #{createdAt}, #{updatedAt})")
+ @Options(keyColumn = "id", useGeneratedKeys = true, keyProperty = "id")
+ void insert(DashboardDto dashboardDto);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetDto.java b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetDto.java
new file mode 100644
index 00000000000..02e0a428163
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetDto.java
@@ -0,0 +1,205 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+public class WidgetDto {
+ private Long id;
+ private Long dashboardId;
+ private String widgetKey;
+ private String name;
+ private String description;
+ private Integer columnIndex;
+ private Integer rowIndex;
+ private boolean configured;
+ private Integer resourceId;
+ private Date createdAt;
+ private Date updatedAt;
+ private List<WidgetPropertyDto> widgetPropertyDtos = Lists.newArrayList();
+
+ /**
+ * @return the id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public WidgetDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * @return the dashboardId
+ */
+ public Long getDashboardId() {
+ return dashboardId;
+ }
+
+ /**
+ * @param dashboardId the dashboardId to set
+ */
+ public WidgetDto setDashboardId(Long dashboardId) {
+ this.dashboardId = dashboardId;
+ return this;
+ }
+
+ public String getWidgetKey() {
+ return widgetKey;
+ }
+
+ public WidgetDto setWidgetKey(String s) {
+ this.widgetKey = s;
+ return this;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public WidgetDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @param description the description to set
+ */
+ public WidgetDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * @return the columnIndex
+ */
+ public Integer getColumnIndex() {
+ return columnIndex;
+ }
+
+ /**
+ * @param columnIndex the columnIndex to set
+ */
+ public WidgetDto setColumnIndex(Integer columnIndex) {
+ this.columnIndex = columnIndex;
+ return this;
+ }
+
+ /**
+ * @return the rowIndex
+ */
+ public Integer getRowIndex() {
+ return rowIndex;
+ }
+
+ /**
+ * @param rowIndex the rowIndex to set
+ */
+ public WidgetDto setRowIndex(Integer rowIndex) {
+ this.rowIndex = rowIndex;
+ return this;
+ }
+
+ /**
+ * @return the configured
+ */
+ public boolean getConfigured() {
+ return configured;
+ }
+
+ /**
+ * @param configured the configured to set
+ */
+ public WidgetDto setConfigured(boolean configured) {
+ this.configured = configured;
+ return this;
+ }
+
+ /**
+ * @return the widgetProperties
+ */
+ public Collection<WidgetPropertyDto> getWidgetProperties() {
+ return widgetPropertyDtos;
+ }
+
+ /**
+ * @param widgetPropertyDto the widgetProperty to set
+ */
+ public WidgetDto addWidgetProperty(WidgetPropertyDto widgetPropertyDto) {
+ widgetPropertyDtos.add(widgetPropertyDto);
+ return this;
+ }
+
+ /**
+ * @return the resourceId
+ * @since 3.1
+ */
+ public Integer getResourceId() {
+ return resourceId;
+ }
+
+ /**
+ * @param resourceId the resourceId to set
+ * @since 3.1
+ */
+ public WidgetDto setResourceId(Integer resourceId) {
+ this.resourceId = resourceId;
+ return this;
+ }
+
+ public WidgetDto setCreatedAt(Date datetime) {
+ this.createdAt = datetime;
+ return this;
+ }
+
+ public WidgetDto setUpdatedAt(Date datetime) {
+ this.updatedAt = datetime;
+ return this;
+ }
+
+ public final Date getCreatedAt() {
+ return this.createdAt;
+ }
+
+ public final Date getUpdatedAt() {
+ return this.updatedAt;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetMapper.java b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetMapper.java
new file mode 100644
index 00000000000..c82e1b4c924
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetMapper.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import java.util.Collection;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Update;
+
+public interface WidgetMapper {
+
+ @Insert("insert into widgets (dashboard_id, widget_key, name, description, column_index, " +
+ " row_index, configured, created_at, updated_at, resource_id)" +
+ " values (#{dashboardId,jdbcType=INTEGER}, #{widgetKey,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, " +
+ " #{description,jdbcType=VARCHAR}, #{columnIndex,jdbcType=INTEGER}, " +
+ " #{rowIndex,jdbcType=INTEGER}, #{configured,jdbcType=BOOLEAN}, #{createdAt,jdbcType=TIMESTAMP}, #{updatedAt,jdbcType=TIMESTAMP}, #{resourceId,jdbcType=INTEGER})")
+ @Options(keyColumn = "id", useGeneratedKeys = true, keyProperty = "id")
+ void insert(WidgetDto widgetDto);
+
+ WidgetDto selectById(long widgetId);
+
+ Collection<WidgetDto> selectByDashboard(long dashboardKey);
+
+ Collection<WidgetDto> selectAll();
+
+ @Update("UPDATE widgets SET " +
+ "dashboard_id=#{dashboardId,jdbcType=INTEGER}, " +
+ "widget_key=#{widgetKey,jdbcType=VARCHAR}, " +
+ "name=#{name,jdbcType=VARCHAR}, " +
+ "description=#{description,jdbcType=VARCHAR}, " +
+ "column_index=#{columnIndex,jdbcType=INTEGER}, " +
+ "row_index=#{rowIndex,jdbcType=INTEGER}, " +
+ "configured=#{configured,jdbcType=BOOLEAN}, " +
+ "created_at=#{createdAt,jdbcType=TIMESTAMP}, " +
+ "updated_at=#{updatedAt,jdbcType=TIMESTAMP}, " +
+ "resource_id=#{resourceId,jdbcType=INTEGER} " +
+ "WHERE id=#{id}")
+ @Options(useGeneratedKeys = false)
+ void update(WidgetDto item);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyDto.java b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyDto.java
new file mode 100644
index 00000000000..0cbf5caa77c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyDto.java
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.Collection;
+
+public class WidgetPropertyDto {
+ private Long id;
+ private Long widgetId;
+ private String propertyKey;
+ private String textValue;
+
+ public Long getId() {
+ return id;
+ }
+
+ public WidgetPropertyDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getWidgetId() {
+ return widgetId;
+ }
+
+ public WidgetPropertyDto setWidgetId(Long widgetId) {
+ this.widgetId = widgetId;
+ return this;
+ }
+
+ public String getPropertyKey() {
+ return propertyKey;
+ }
+
+ public WidgetPropertyDto setPropertyKey(String s) {
+ this.propertyKey = s;
+ return this;
+ }
+
+ public String getTextValue() {
+ return textValue;
+ }
+
+ public WidgetPropertyDto setTextValue(String s) {
+ this.textValue = s;
+ return this;
+ }
+
+ public static ListMultimap<Long, WidgetPropertyDto> groupByWidgetId(Collection<WidgetPropertyDto> properties) {
+ ListMultimap<Long, WidgetPropertyDto> group = ArrayListMultimap.create();
+ for (WidgetPropertyDto property : properties) {
+ group.put(property.getWidgetId(), property);
+ }
+ return group;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyMapper.java b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyMapper.java
new file mode 100644
index 00000000000..46432fba6b8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/WidgetPropertyMapper.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Select;
+
+public interface WidgetPropertyMapper {
+
+ String COLUMNS = "wp.id, wp.widget_id as \"widgetId\", wp.kee as \"propertyKey\", wp.text_value as \"textValue\"";
+
+ @Insert("insert into widget_properties (widget_id, kee, text_value) values (#{widgetId}, #{propertyKey}, #{textValue})")
+ @Options(keyColumn = "id", useGeneratedKeys = true, keyProperty = "id")
+ void insert(WidgetPropertyDto dto);
+
+ @CheckForNull
+ @Select("select " + COLUMNS + " from widget_properties wp where wp.id=#{id}")
+ WidgetPropertyDto selectById(long propertyId);
+
+ @Select("select " + COLUMNS + " from widget_properties wp " +
+ "inner join widgets w on w.id=wp.widget_id where w.dashboard_id=#{id}")
+ Collection<WidgetPropertyDto> selectByDashboard(long dashboardKey);
+
+ void deleteByWidgetIds(List<Long> widgetIds);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dashboard/package-info.java b/sonar-db/src/main/java/org/sonar/db/dashboard/package-info.java
new file mode 100644
index 00000000000..389a1fac572
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dashboard/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.dashboard;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDao.java b/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDao.java
new file mode 100644
index 00000000000..789c94a1f12
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDao.java
@@ -0,0 +1,221 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.debt;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class CharacteristicDao implements Dao {
+
+ private final MyBatis mybatis;
+
+ public CharacteristicDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ /**
+ * @return enabled root characteristics and characteristics
+ */
+ public List<CharacteristicDto> selectEnabledCharacteristics() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectEnabledCharacteristics(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<CharacteristicDto> selectEnabledCharacteristics(SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectEnabledCharacteristics();
+ }
+
+ /**
+ * @return all characteristics
+ */
+ public List<CharacteristicDto> selectCharacteristics() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectCharacteristics(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<CharacteristicDto> selectCharacteristics(SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectCharacteristics();
+ }
+
+ /**
+ * @return only enabled root characteristics, order by order
+ */
+ public List<CharacteristicDto> selectEnabledRootCharacteristics() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectEnabledRootCharacteristics(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * @return only enabled root characteristics, order by order
+ */
+ public List<CharacteristicDto> selectEnabledRootCharacteristics(SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectEnabledRootCharacteristics();
+ }
+
+ public List<CharacteristicDto> selectCharacteristicsByParentId(int parentId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectCharacteristicsByParentId(parentId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<CharacteristicDto> selectCharacteristicsByParentId(int parentId, SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectCharacteristicsByParentId(parentId);
+ }
+
+ public List<CharacteristicDto> selectCharacteristicsByIds(Collection<Integer> ids) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectCharacteristicsByIds(ids, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<CharacteristicDto> selectCharacteristicsByIds(Collection<Integer> ids, SqlSession session) {
+ List<CharacteristicDto> dtos = newArrayList();
+ List<List<Integer>> partitionList = Lists.partition(newArrayList(ids), 1000);
+ for (List<Integer> partition : partitionList) {
+ dtos.addAll(session.getMapper(CharacteristicMapper.class).selectCharacteristicsByIds(partition));
+ }
+ return dtos;
+ }
+
+ @CheckForNull
+ public CharacteristicDto selectByKey(String key) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectByKey(key, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public CharacteristicDto selectByKey(String key, SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectByKey(key);
+ }
+
+ @CheckForNull
+ public CharacteristicDto selectById(int id) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectById(id, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public CharacteristicDto selectById(int id, SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectById(id);
+ }
+
+ @CheckForNull
+ public CharacteristicDto selectByName(String name) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectByName(name, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public CharacteristicDto selectByName(String name, SqlSession session) {
+ return session.getMapper(CharacteristicMapper.class).selectByName(name);
+ }
+
+ public int selectMaxCharacteristicOrder() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectMaxCharacteristicOrder(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public int selectMaxCharacteristicOrder(SqlSession session) {
+ Integer result = session.getMapper(CharacteristicMapper.class).selectMaxCharacteristicOrder();
+ return result != null ? result : 0;
+ }
+
+ public void insert(CharacteristicDto dto, SqlSession session) {
+ session.getMapper(CharacteristicMapper.class).insert(dto);
+ }
+
+ public void insert(CharacteristicDto dto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ insert(dto, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(DbSession session, Collection<CharacteristicDto> items) {
+ for (CharacteristicDto item : items) {
+ insert(item, session);
+ }
+ }
+
+ public void insert(DbSession session, CharacteristicDto item, CharacteristicDto... others) {
+ insert(session, Lists.asList(item, others));
+ }
+
+ public void update(CharacteristicDto dto, SqlSession session) {
+ session.getMapper(CharacteristicMapper.class).update(dto);
+ }
+
+ public void update(CharacteristicDto dto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ update(dto, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDto.java b/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDto.java
new file mode 100644
index 00000000000..3cbaff45e81
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicDto.java
@@ -0,0 +1,138 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.debt;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+
+public class CharacteristicDto implements Serializable {
+
+ private Integer id;
+ private String kee;
+ private String name;
+ private Integer parentId;
+ private Integer characteristicOrder;
+ private Date createdAt;
+ private Date updatedAt;
+ private boolean enabled;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public CharacteristicDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey() {
+ return kee;
+ }
+
+ public CharacteristicDto setKey(String s) {
+ this.kee = s;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public CharacteristicDto setName(String s) {
+ this.name = s;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getParentId() {
+ return parentId;
+ }
+
+ public CharacteristicDto setParentId(@Nullable Integer i) {
+ this.parentId = i;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getOrder() {
+ return characteristicOrder;
+ }
+
+ public CharacteristicDto setOrder(@Nullable Integer i) {
+ this.characteristicOrder = i;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public CharacteristicDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public CharacteristicDto setUpdatedAt(@Nullable Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public CharacteristicDto setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ public DefaultCharacteristic toCharacteristic(@Nullable DefaultCharacteristic parent) {
+ return new DefaultCharacteristic()
+ .setId(id)
+ .setKey(kee)
+ .setName(name)
+ .setOrder(characteristicOrder)
+ .setParent(parent)
+ .setRoot(parent)
+ .setCreatedAt(createdAt)
+ .setUpdatedAt(updatedAt);
+ }
+
+ public static CharacteristicDto toDto(DefaultCharacteristic characteristic, @Nullable Integer parentId) {
+ return new CharacteristicDto()
+ .setKey(characteristic.key())
+ .setName(characteristic.name())
+ .setOrder(characteristic.order())
+ .setParentId(parentId)
+ .setEnabled(true)
+ .setCreatedAt(characteristic.createdAt())
+ .setUpdatedAt(characteristic.updatedAt());
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicMapper.java b/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicMapper.java
new file mode 100644
index 00000000000..20feb8641ab
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/debt/CharacteristicMapper.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.debt;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface CharacteristicMapper {
+
+ List<CharacteristicDto> selectEnabledCharacteristics();
+
+ List<CharacteristicDto> selectCharacteristics();
+
+ List<CharacteristicDto> selectEnabledRootCharacteristics();
+
+ List<CharacteristicDto> selectCharacteristicsByParentId(int parentId);
+
+ List<CharacteristicDto> selectCharacteristicsByIds(@Param("ids") List<Integer> ids);
+
+ CharacteristicDto selectByKey(String key);
+
+ CharacteristicDto selectById(int id);
+
+ CharacteristicDto selectByName(String name);
+
+ Integer selectMaxCharacteristicOrder();
+
+ void insert(CharacteristicDto characteristic);
+
+ int update(CharacteristicDto characteristic);
+
+ void deleteRequirementsFromCharacteristicsTable();
+
+ List<RequirementMigrationDto> selectDeprecatedRequirements();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/debt/RequirementMigrationDto.java b/sonar-db/src/main/java/org/sonar/db/debt/RequirementMigrationDto.java
new file mode 100644
index 00000000000..f2dfe90d85d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/debt/RequirementMigrationDto.java
@@ -0,0 +1,159 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.debt;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * Only used in {@link org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules}
+ */
+public class RequirementMigrationDto implements Serializable {
+
+ private Integer id;
+ private Integer parentId;
+ private Integer rootId;
+ private Integer ruleId;
+ private String functionKey;
+ private Double coefficientValue;
+ private String coefficientUnit;
+ private Double offsetValue;
+ private String offsetUnit;
+ private Date createdAt;
+ private Date updatedAt;
+ private boolean enabled;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public RequirementMigrationDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public Integer getParentId() {
+ return parentId;
+ }
+
+ public RequirementMigrationDto setParentId(Integer i) {
+ this.parentId = i;
+ return this;
+ }
+
+ public Integer getRootId() {
+ return rootId;
+ }
+
+ public RequirementMigrationDto setRootId(Integer rootId) {
+ this.rootId = rootId;
+ return this;
+ }
+
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ public RequirementMigrationDto setRuleId(Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ public String getFunction() {
+ return functionKey;
+ }
+
+ public RequirementMigrationDto setFunction(String function) {
+ this.functionKey = function;
+ return this;
+ }
+
+ @CheckForNull
+ public Double getCoefficientValue() {
+ return coefficientValue;
+ }
+
+ public RequirementMigrationDto setCoefficientValue(@Nullable Double coefficientValue) {
+ this.coefficientValue = coefficientValue;
+ return this;
+ }
+
+ @CheckForNull
+ public String getCoefficientUnit() {
+ return coefficientUnit;
+ }
+
+ public RequirementMigrationDto setCoefficientUnit(@Nullable String coefficientUnit) {
+ this.coefficientUnit = coefficientUnit;
+ return this;
+ }
+
+ @CheckForNull
+ public Double getOffsetValue() {
+ return offsetValue;
+ }
+
+ public RequirementMigrationDto setOffsetValue(@Nullable Double offset) {
+ this.offsetValue = offset;
+ return this;
+ }
+
+ @CheckForNull
+ public String getOffsetUnit() {
+ return offsetUnit;
+ }
+
+ public RequirementMigrationDto setOffsetUnit(@Nullable String offsetUnit) {
+ this.offsetUnit = offsetUnit;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public RequirementMigrationDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public RequirementMigrationDto setUpdatedAt(@Nullable Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public RequirementMigrationDto setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/debt/package-info.java b/sonar-db/src/main/java/org/sonar/db/debt/package-info.java
new file mode 100644
index 00000000000..b1e34d83234
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/debt/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.debt;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/deprecated/ClusterAction.java b/sonar-db/src/main/java/org/sonar/db/deprecated/ClusterAction.java
new file mode 100644
index 00000000000..d6bfb3b30ab
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/deprecated/ClusterAction.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.deprecated;
+
+import java.util.concurrent.Callable;
+
+public interface ClusterAction<K> extends Callable<K> {
+
+ @Override
+ public K call() throws Exception;
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/deprecated/NullQueue.java b/sonar-db/src/main/java/org/sonar/db/deprecated/NullQueue.java
new file mode 100644
index 00000000000..db002f476c0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/deprecated/NullQueue.java
@@ -0,0 +1,30 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.deprecated;
+
+import java.util.List;
+
+public class NullQueue implements WorkQueue<ClusterAction> {
+
+ @Override
+ public void enqueue(List<ClusterAction> actions) {
+ // do nothing
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/deprecated/WorkQueue.java b/sonar-db/src/main/java/org/sonar/db/deprecated/WorkQueue.java
new file mode 100644
index 00000000000..3b4e2077771
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/deprecated/WorkQueue.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.deprecated;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+public interface WorkQueue<K extends Callable> {
+
+ void enqueue(List<K> actions);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java b/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java
new file mode 100644
index 00000000000..236c90acd1b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/AbstractDialect.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 3.2
+ */
+abstract class AbstractDialect implements Dialect {
+ private final String id;
+ private final String activeRecordDialectCode;
+ private final String defaultDriverClassName;
+ private final String trueSqlValue;
+ private final String falseSqlValue;
+ private final String validationQuery;
+
+ protected AbstractDialect(String id, String activeRecordDialectCode, String defaultDriverClassName, String trueSqlValue, String falseSqlValue,
+ String validationQuery) {
+ this.id = id;
+ this.activeRecordDialectCode = activeRecordDialectCode;
+ this.defaultDriverClassName = defaultDriverClassName;
+ this.trueSqlValue = trueSqlValue;
+ this.falseSqlValue = falseSqlValue;
+ this.validationQuery = validationQuery;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String getActiveRecordDialectCode() {
+ return activeRecordDialectCode;
+ }
+
+ @Override
+ public String getDefaultDriverClassName() {
+ return defaultDriverClassName;
+ }
+
+ @Override
+ public final String getTrueSqlValue() {
+ return trueSqlValue;
+ }
+
+ @Override
+ public final String getFalseSqlValue() {
+ return falseSqlValue;
+ }
+
+ @Override
+ public final String getValidationQuery() {
+ return validationQuery;
+ }
+
+ @Override
+ public List<String> getConnectionInitStatements() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public int getScrollDefaultFetchSize() {
+ return 200;
+ }
+
+ @Override
+ public int getScrollSingleRowFetchSize() {
+ return 1;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java b/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java
new file mode 100644
index 00000000000..3d0a0f6e730
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/Dialect.java
@@ -0,0 +1,92 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import java.util.List;
+
+/**
+ * @since 1.12
+ */
+public interface Dialect {
+
+ /**
+ * @return the sonar dialect Id to be matched with the sonar.jdbc.dialect property when provided
+ */
+ String getId();
+
+ /**
+ * @return the activerecord dialect to be used
+ */
+ String getActiveRecordDialectCode();
+
+ /**
+ * Used to autodetect a dialect for a given driver URL
+ *
+ * @param jdbcConnectionURL a jdbc driver url such as jdbc:mysql://localhost:3306/sonar
+ * @return true if the dialect supports surch url
+ */
+ boolean matchesJdbcURL(String jdbcConnectionURL);
+
+ /**
+ * @since 2.13
+ */
+ String getDefaultDriverClassName();
+
+ List<String> getConnectionInitStatements();
+
+ /**
+ * @since 2.14
+ */
+ String getTrueSqlValue();
+
+ /**
+ * @since 2.14
+ */
+ String getFalseSqlValue();
+
+ /**
+ * Query used to validate the jdbc connection.
+ *
+ * @since 3.2
+ */
+ String getValidationQuery();
+
+ /**
+ * Fetch size to be used when scrolling large result sets.
+ *
+ * @since 5.0
+ */
+ int getScrollDefaultFetchSize();
+
+ /**
+ * Fetch size to scroll one row at a time. It sounds strange because obviously value is 1 in most cases,
+ * but it's different on MySQL...
+ *
+ * @since 5.0
+ */
+ int getScrollSingleRowFetchSize();
+
+ /**
+ * Indicates whether DB migration can be perform on the DB vendor implementation associated with the current dialect.
+ *
+ * @return a boolean
+ */
+ boolean supportsMigration();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/DialectUtils.java b/sonar-db/src/main/java/org/sonar/db/dialect/DialectUtils.java
new file mode 100644
index 00000000000..d000cc78151
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/DialectUtils.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterators;
+import java.util.NoSuchElementException;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.MessageException;
+
+public final class DialectUtils {
+
+ private DialectUtils() {
+ // only static stuff
+ }
+
+ private static final Dialect[] DIALECTS = new Dialect[] {new H2(), new MySql(), new Oracle(), new PostgreSql(), new MsSql()};
+
+ public static Dialect find(final String dialectId, final String jdbcConnectionUrl) {
+ Dialect match = StringUtils.isNotBlank(dialectId) ? findById(dialectId) : findByJdbcUrl(jdbcConnectionUrl);
+ if (match == null) {
+ throw MessageException.of("Unable to determine database dialect to use within sonar with dialect " + dialectId + " jdbc url " + jdbcConnectionUrl);
+ }
+ return match;
+ }
+
+ @CheckForNull
+ private static Dialect findByJdbcUrl(final String jdbcConnectionUrl) {
+ return findDialect(new Predicate<Dialect>() {
+ @Override
+ public boolean apply(@Nullable Dialect dialect) {
+ return dialect != null && dialect.matchesJdbcURL(StringUtils.trimToEmpty(jdbcConnectionUrl));
+ }
+ });
+ }
+
+ @CheckForNull
+ private static Dialect findById(final String dialectId) {
+ return findDialect(new Predicate<Dialect>() {
+ @Override
+ public boolean apply(@Nullable Dialect dialect) {
+ return dialect != null && dialect.getId().equals(dialectId);
+ }
+ });
+ }
+
+ @CheckForNull
+ private static Dialect findDialect(Predicate<Dialect> predicate) {
+ try {
+ return Iterators.find(Iterators.forArray(DIALECTS), predicate);
+ } catch (NoSuchElementException ex) {
+ return null;
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/H2.java b/sonar-db/src/main/java/org/sonar/db/dialect/H2.java
new file mode 100644
index 00000000000..aa9bd6fb881
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/H2.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 1.12
+ */
+public class H2 extends AbstractDialect {
+
+ public static final String ID = "h2";
+
+ public H2() {
+ super(ID, ".h2.", "org.h2.Driver", "true", "false", "SELECT 1");
+ }
+
+ @Override
+ public boolean matchesJdbcURL(String jdbcConnectionURL) {
+ return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:h2:");
+ }
+
+ @Override
+ public boolean supportsMigration() {
+ return false;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/MsSql.java b/sonar-db/src/main/java/org/sonar/db/dialect/MsSql.java
new file mode 100644
index 00000000000..d55139113c2
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/MsSql.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.apache.commons.lang.StringUtils;
+
+public class MsSql extends AbstractDialect {
+
+ public static final String ID = "mssql";
+
+ public MsSql() {
+ super(ID, "sqlserver", "net.sourceforge.jtds.jdbc.Driver", "1", "0", "SELECT 1");
+ }
+
+ @Override
+ public boolean matchesJdbcURL(String jdbcConnectionURL) {
+ return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:microsoft:sqlserver:")
+ || StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:jtds:sqlserver:");
+ }
+
+ @Override
+ public boolean supportsMigration() {
+ return true;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/MySql.java b/sonar-db/src/main/java/org/sonar/db/dialect/MySql.java
new file mode 100644
index 00000000000..66019cb785f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/MySql.java
@@ -0,0 +1,54 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 1.12
+ */
+public class MySql extends AbstractDialect {
+
+ public static final String ID = "mysql";
+
+ public MySql() {
+ super(ID, "mysql", "com.mysql.jdbc.Driver", "true", "false", "SELECT 1");
+ }
+
+ @Override
+ public boolean matchesJdbcURL(String jdbcConnectionURL) {
+ return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:mysql:");
+ }
+
+ @Override
+ public int getScrollDefaultFetchSize() {
+ return Integer.MIN_VALUE;
+ }
+
+ @Override
+ public int getScrollSingleRowFetchSize() {
+ return Integer.MIN_VALUE;
+ }
+
+ @Override
+ public boolean supportsMigration() {
+ return true;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/Oracle.java b/sonar-db/src/main/java/org/sonar/db/dialect/Oracle.java
new file mode 100644
index 00000000000..dd5fccadf4b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/Oracle.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 1.12
+ */
+public class Oracle extends AbstractDialect {
+
+ public static final String ID = "oracle";
+
+ public Oracle() {
+ super(ID, "oracle", "oracle.jdbc.OracleDriver", "1", "0", "SELECT 1 FROM DUAL");
+ }
+
+ @Override
+ public boolean matchesJdbcURL(String jdbcConnectionURL) {
+ return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:oracle:");
+ }
+
+ @Override
+ public boolean supportsMigration() {
+ return true;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/PostgreSql.java b/sonar-db/src/main/java/org/sonar/db/dialect/PostgreSql.java
new file mode 100644
index 00000000000..d3aaadb92cb
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/PostgreSql.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 1.12
+ */
+public class PostgreSql extends AbstractDialect {
+
+ public static final String ID = "postgresql";
+ static final List<String> INIT_STATEMENTS = ImmutableList.of("SET standard_conforming_strings=on", "SET backslash_quote=off");
+
+ public PostgreSql() {
+ super(ID, "postgre", "org.postgresql.Driver", "true", "false", "SELECT 1");
+ }
+
+ @Override
+ public boolean matchesJdbcURL(String jdbcConnectionURL) {
+ return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:postgresql:");
+ }
+
+ @Override
+ public List<String> getConnectionInitStatements() {
+ return INIT_STATEMENTS;
+ }
+
+ @Override
+ public boolean supportsMigration() {
+ return true;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/dialect/package-info.java b/sonar-db/src/main/java/org/sonar/db/dialect/package-info.java
new file mode 100644
index 00000000000..a28f4911544
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/dialect/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.dialect;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java
new file mode 100644
index 00000000000..25690f35569
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationDao.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.duplication;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class DuplicationDao {
+
+ private final MyBatis mybatis;
+
+ public DuplicationDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public List<DuplicationUnitDto> selectCandidates(int resourceSnapshotId, Integer lastSnapshotId, String language) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ DuplicationMapper mapper = session.getMapper(DuplicationMapper.class);
+ return mapper.selectCandidates(resourceSnapshotId, lastSnapshotId, language);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Insert rows in the table DUPLICATIONS_INDEX.
+ * Note that generated ids are not returned.
+ */
+ public void insert(Collection<DuplicationUnitDto> units) {
+ DbSession session = mybatis.openSession(true);
+ try {
+ DuplicationMapper mapper = session.getMapper(DuplicationMapper.class);
+ for (DuplicationUnitDto unit : units) {
+ mapper.batchInsert(unit);
+ }
+ session.commit();
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java
new file mode 100644
index 00000000000..fe366d9ffc0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationMapper.java
@@ -0,0 +1,34 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.duplication;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface DuplicationMapper {
+
+ List<DuplicationUnitDto> selectCandidates(
+ @Param("resource_snapshot_id") int resourceSnapshotId,
+ @Param("last_project_snapshot_id") Integer lastSnapshotId,
+ @Param("language") String language);
+
+ void batchInsert(DuplicationUnitDto unit);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java
new file mode 100644
index 00000000000..443782a63bd
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/duplication/DuplicationUnitDto.java
@@ -0,0 +1,115 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.duplication;
+
+/**
+ * A simple DTO (Data Transfer Object) class that provides the mapping of data to a table.
+ */
+public final class DuplicationUnitDto {
+
+ private Long id;
+ private Integer snapshotId;
+ private Integer projectSnapshotId;
+
+ private String hash;
+ private int indexInFile;
+ private int startLine;
+ private int endLine;
+
+ private String resourceKey;
+
+ public DuplicationUnitDto() {
+ }
+
+ public DuplicationUnitDto(Integer projectSnapshotId, Integer snapshotId, String hash, Integer indexInFile, Integer startLine, Integer endLine) {
+ this.projectSnapshotId = projectSnapshotId;
+ this.snapshotId = snapshotId;
+ this.hash = hash;
+ this.indexInFile = indexInFile;
+ this.startLine = startLine;
+ this.endLine = endLine;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public DuplicationUnitDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Integer getSnapshotId() {
+ return snapshotId;
+ }
+
+ public void setSnapshotId(Integer snapshotId) {
+ this.snapshotId = snapshotId;
+ }
+
+ public Integer getProjectSnapshotId() {
+ return projectSnapshotId;
+ }
+
+ public void setProjectSnapshotId(Integer projectSnapshotId) {
+ this.projectSnapshotId = projectSnapshotId;
+ }
+
+ public String getHash() {
+ return hash;
+ }
+
+ public void setHash(String hash) {
+ this.hash = hash;
+ }
+
+ public int getIndexInFile() {
+ return indexInFile;
+ }
+
+ public void setIndexInFile(int indexInFile) {
+ this.indexInFile = indexInFile;
+ }
+
+ public int getStartLine() {
+ return startLine;
+ }
+
+ public void setStartLine(int startLine) {
+ this.startLine = startLine;
+ }
+
+ public int getEndLine() {
+ return endLine;
+ }
+
+ public void setEndLine(int endLine) {
+ this.endLine = endLine;
+ }
+
+ public String getResourceKey() {
+ return resourceKey;
+ }
+
+ public void setResourceKey(String resourceKey) {
+ this.resourceKey = resourceKey;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/duplication/package-info.java b/sonar-db/src/main/java/org/sonar/db/duplication/package-info.java
new file mode 100644
index 00000000000..54a5e199058
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/duplication/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.duplication;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/event/EventDto.java b/sonar-db/src/main/java/org/sonar/db/event/EventDto.java
new file mode 100644
index 00000000000..884723ab408
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/event/EventDto.java
@@ -0,0 +1,132 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.event;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class EventDto {
+
+ public static final String CATEGORY_VERSION = "Version";
+ public static final String CATEGORY_ALERT = "Alert";
+ public static final String CATEGORY_PROFILE = "Profile";
+
+ private Long id;
+
+ private String name;
+
+ private String description;
+
+ private String category;
+
+ private Long date;
+
+ private Long createdAt;
+
+ private String data;
+
+ private Long snapshotId;
+
+ private String componentUuid;
+
+ public Long getId() {
+ return id;
+ }
+
+ public EventDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public EventDto setCategory(String category) {
+ this.category = category;
+ return this;
+ }
+
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ public EventDto setComponentUuid(String componentUuid) {
+ this.componentUuid = componentUuid;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ public EventDto setCreatedAt(Long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ @CheckForNull
+ public String getData() {
+ return data;
+ }
+
+ public EventDto setData(@Nullable String data) {
+ this.data = data;
+ return this;
+ }
+
+ public Long getDate() {
+ return date;
+ }
+
+ public EventDto setDate(Long date) {
+ this.date = date;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public EventDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public EventDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Long getSnapshotId() {
+ return snapshotId;
+ }
+
+ public EventDto setSnapshotId(Long snapshotId) {
+ this.snapshotId = snapshotId;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java b/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java
new file mode 100644
index 00000000000..57dee6a9e68
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.event;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface EventMapper {
+
+ List<EventDto> selectByComponentUuid(String uuid);
+
+ void insert(EventDto dto);
+
+ void delete(long id);
+
+ /**
+ * TODO Used by PastSnapshotFinderByVersion. Should be dropped soon.
+ */
+ @CheckForNull
+ Long findSnapshotIdOfPreviousVersion(@Param("componentId") long componentId, @Param("currentVersion") String currentVersion);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/event/package-info.java b/sonar-db/src/main/java/org/sonar/db/event/package-info.java
new file mode 100644
index 00000000000..1e1d8997b92
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/event/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.event;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDao.java b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDao.java
new file mode 100644
index 00000000000..bb63a3c9b90
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDao.java
@@ -0,0 +1,118 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.Dao;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+@ServerSide
+public class ActionPlanDao implements Dao {
+
+ private final MyBatis mybatis;
+
+ public ActionPlanDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public void save(ActionPlanDto actionPlanDto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ session.getMapper(ActionPlanMapper.class).insert(actionPlanDto);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void update(ActionPlanDto actionPlanDto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ session.getMapper(ActionPlanMapper.class).update(actionPlanDto);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(String key) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ session.getMapper(ActionPlanMapper.class).delete(key);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public ActionPlanDto findByKey(String key) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ActionPlanMapper.class).findByKey(key);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ActionPlanDto> findByKeys(Collection<String> keys) {
+ if (keys.isEmpty()) {
+ return Collections.emptyList();
+ }
+ SqlSession session = mybatis.openSession(false);
+ try {
+ List<ActionPlanDto> dtosList = newArrayList();
+ List<List<String>> keysPartition = Lists.partition(newArrayList(keys), 1000);
+ for (List<String> partition : keysPartition) {
+ List<ActionPlanDto> dtos = session.getMapper(ActionPlanMapper.class).findByKeys(partition);
+ dtosList.addAll(dtos);
+ }
+ return dtosList;
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ActionPlanDto> findOpenByProjectId(Long projectId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ActionPlanMapper.class).findOpenByProjectId(projectId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ActionPlanDto> findByNameAndProjectId(String name, Long projectId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ActionPlanMapper.class).findByNameAndProjectId(name, projectId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDto.java b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDto.java
new file mode 100644
index 00000000000..f9cce188c07
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanDto.java
@@ -0,0 +1,201 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.issue.ActionPlan;
+import org.sonar.core.issue.DefaultActionPlan;
+
+/**
+ * @since 3.6
+ */
+public class ActionPlanDto {
+
+ private Long id;
+ private String kee;
+ private String name;
+ private String description;
+ private String userLogin;
+ private Long projectId;
+ private String status;
+ private Date deadLine;
+ private Date createdAt;
+ private Date updatedAt;
+
+ // return by joins
+ private String projectKey;
+
+ public Long getId() {
+ return id;
+ }
+
+ public ActionPlanDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey() {
+ return kee;
+ }
+
+ public ActionPlanDto setKey(String kee) {
+ this.kee = kee;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ActionPlanDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public ActionPlanDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public ActionPlanDto setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public ActionPlanDto setProjectId(Long projectId) {
+ this.projectId = projectId;
+ return this;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public ActionPlanDto setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getDeadLine() {
+ return deadLine;
+ }
+
+ public ActionPlanDto setDeadLine(@Nullable Date deadLine) {
+ this.deadLine = deadLine;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public ActionPlanDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public ActionPlanDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public String getProjectKey() {
+ return projectKey;
+ }
+
+ /**
+ * Only for unit tests
+ */
+ public ActionPlanDto setProjectKey_unit_test_only(String projectKey) {
+ this.projectKey = projectKey;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ActionPlanDto actionPlanDto = (ActionPlanDto) o;
+ return !((id != null) ? !id.equals(actionPlanDto.id) : (actionPlanDto.id != null));
+ }
+
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public DefaultActionPlan toActionPlan() {
+ return new DefaultActionPlan()
+ .setName(name)
+ .setKey(kee)
+ .setProjectKey(projectKey)
+ .setDescription(description)
+ .setStatus(status)
+ .setDeadLine(deadLine)
+ .setUserLogin(userLogin)
+ .setCreatedAt(createdAt)
+ .setUpdatedAt(updatedAt);
+ }
+
+ public static ActionPlanDto toActionDto(ActionPlan actionPlan, Long projectId) {
+ return new ActionPlanDto().setKey(actionPlan.key())
+ .setName(actionPlan.name())
+ .setProjectId(projectId)
+ .setDescription(actionPlan.description())
+ .setStatus(actionPlan.status())
+ .setDeadLine(actionPlan.deadLine())
+ .setUserLogin(actionPlan.userLogin())
+ .setCreatedAt(actionPlan.createdAt())
+ .setUpdatedAt(actionPlan.updatedAt());
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanMapper.java b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanMapper.java
new file mode 100644
index 00000000000..b8df2a11a58
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanMapper.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @since 3.6
+ */
+public interface ActionPlanMapper {
+
+ void insert(ActionPlanDto actionPlanDto);
+
+ void update(ActionPlanDto actionPlanDto);
+
+ void delete(@Param("key") String key);
+
+ List<ActionPlanDto> findByKeys(@Param("keys") List<String> keys);
+
+ ActionPlanDto findByKey(@Param("key") String key);
+
+ List<ActionPlanDto> findOpenByProjectId(@Param("projectId") Long projectId);
+
+ List<ActionPlanDto> findByNameAndProjectId(@Param("name") String name, @Param("projectId") Long projectId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDao.java b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDao.java
new file mode 100644
index 00000000000..dfd4192922b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDao.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+public class ActionPlanStatsDao {
+
+ private final MyBatis mybatis;
+
+ public ActionPlanStatsDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public List<ActionPlanStatsDto> findByProjectId(Long projectId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ActionPlanStatsMapper.class).findByProjectId(projectId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDto.java b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDto.java
new file mode 100644
index 00000000000..d1aa6bc3797
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsDto.java
@@ -0,0 +1,187 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.Date;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.core.issue.ActionPlanStats;
+
+/**
+ * @since 3.6
+ */
+public class ActionPlanStatsDto {
+
+ private Integer id;
+ private String kee;
+ private String name;
+ private String description;
+ private String userLogin;
+ private Integer projectId;
+ private String status;
+ private Date deadLine;
+ private Date createdAt;
+ private Date updatedAt;
+ private int totalIssues;
+ private int unresolvedIssues;
+ // return by joins
+ private String projectKey;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public ActionPlanStatsDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKee() {
+ return kee;
+ }
+
+ public ActionPlanStatsDto setKee(String kee) {
+ this.kee = kee;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ActionPlanStatsDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public ActionPlanStatsDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public ActionPlanStatsDto setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public Integer getProjectId() {
+ return projectId;
+ }
+
+ public ActionPlanStatsDto setProjectId(Integer projectId) {
+ this.projectId = projectId;
+ return this;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public ActionPlanStatsDto setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ public Date getDeadLine() {
+ return deadLine;
+ }
+
+ public ActionPlanStatsDto setDeadLine(Date deadLine) {
+ this.deadLine = deadLine;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public ActionPlanStatsDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public ActionPlanStatsDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public int getTotalIssues() {
+ return totalIssues;
+ }
+
+ public ActionPlanStatsDto setTotalIssues(int totalIssues) {
+ this.totalIssues = totalIssues;
+ return this;
+ }
+
+ public int getUnresolvedIssues() {
+ return unresolvedIssues;
+ }
+
+ public ActionPlanStatsDto setUnresolvedIssues(int unresolvedIssues) {
+ this.unresolvedIssues = unresolvedIssues;
+ return this;
+ }
+
+ public String getProjectKey() {
+ return projectKey;
+ }
+
+ /**
+ * Only for unit tests
+ */
+ public ActionPlanStatsDto setProjectKey_unit_test_only(String projectKey) {
+ this.projectKey = projectKey;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public ActionPlanStats toActionPlanStat() {
+ return ((ActionPlanStats) ActionPlanStats.create(name)
+ .setKey(kee)
+ .setProjectKey(projectKey)
+ .setDescription(description)
+ .setStatus(status)
+ .setDeadLine(deadLine)
+ .setUserLogin(userLogin)
+ .setCreatedAt(createdAt)
+ .setUpdatedAt(updatedAt))
+ .setTotalIssues(totalIssues)
+ .setUnresolvedIssues(unresolvedIssues);
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsMapper.java b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsMapper.java
new file mode 100644
index 00000000000..26c3395a0f7
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/ActionPlanStatsMapper.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @since 3.6
+ */
+public interface ActionPlanStatsMapper {
+
+ /**
+ * @since3.6
+ */
+ List<ActionPlanStatsDto> findByProjectId(@Param("projectId") Long projectId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDao.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDao.java
new file mode 100644
index 00000000000..076681365eb
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDao.java
@@ -0,0 +1,135 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.asList;
+
+@ServerSide
+public class IssueChangeDao implements Dao {
+
+ private final MyBatis mybatis;
+
+ public IssueChangeDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public List<DefaultIssueComment> selectCommentsByIssues(DbSession session, Collection<String> issueKeys) {
+ List<DefaultIssueComment> comments = Lists.newArrayList();
+ for (IssueChangeDto dto : selectByIssuesAndType(session, issueKeys, IssueChangeDto.TYPE_COMMENT)) {
+ comments.add(dto.toComment());
+ }
+ return comments;
+ }
+
+ public List<FieldDiffs> selectChangelogByIssue(String issueKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ List<FieldDiffs> result = Lists.newArrayList();
+ for (IssueChangeDto dto : selectByIssuesAndType(session, asList(issueKey), IssueChangeDto.TYPE_FIELD_CHANGE)) {
+ result.add(dto.toFieldDiffs());
+ }
+ return result;
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<IssueChangeDto> selectChangelogOfNonClosedIssuesByComponent(String componentUuid) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
+ return mapper.selectChangelogOfNonClosedIssuesByComponent(componentUuid, IssueChangeDto.TYPE_FIELD_CHANGE);
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public DefaultIssueComment selectCommentByKey(String commentKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
+ IssueChangeDto dto = mapper.selectByKeyAndType(commentKey, IssueChangeDto.TYPE_COMMENT);
+ return dto != null ? dto.toComment() : null;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ List<IssueChangeDto> selectByIssuesAndType(DbSession session, Collection<String> issueKeys, String changeType) {
+ if (issueKeys.isEmpty()) {
+ return Collections.emptyList();
+ }
+ IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
+ List<IssueChangeDto> dtosList = newArrayList();
+ List<List<String>> keysPartition = Lists.partition(newArrayList(issueKeys), 1000);
+ for (List<String> partition : keysPartition) {
+ List<IssueChangeDto> dtos = mapper.selectByIssuesAndType(partition, changeType);
+ dtosList.addAll(dtos);
+ }
+ return dtosList;
+ }
+
+ public void insert(DbSession session, IssueChangeDto change) {
+ session.getMapper(IssueChangeMapper.class).insert(change);
+ }
+
+ public boolean delete(String key) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
+ int count = mapper.delete(key);
+ session.commit();
+ return count == 1;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public boolean update(IssueChangeDto change) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
+ int count = mapper.update(change);
+ session.commit();
+ return count == 1;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDto.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDto.java
new file mode 100644
index 00000000000..14bce70b26b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeDto.java
@@ -0,0 +1,190 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * @since 3.6
+ */
+public final class IssueChangeDto implements Serializable {
+
+ public static final String TYPE_FIELD_CHANGE = "diff";
+ public static final String TYPE_COMMENT = "comment";
+
+ private Long id;
+ private String kee;
+ private String issueKey;
+ private String userLogin;
+ private String changeType;
+ private String changeData;
+
+ // technical dates
+ private Long createdAt;
+ private Long updatedAt;
+
+ // functional date
+ private Long issueChangeCreationDate;
+
+ public static IssueChangeDto of(DefaultIssueComment comment) {
+ IssueChangeDto dto = newDto(comment.issueKey());
+ dto.setKey(comment.key());
+ dto.setChangeType(IssueChangeDto.TYPE_COMMENT);
+ dto.setChangeData(comment.markdownText());
+ dto.setUserLogin(comment.userLogin());
+ dto.setIssueChangeCreationDate(comment.createdAt() == null ? null : comment.createdAt().getTime());
+ return dto;
+ }
+
+ public static IssueChangeDto of(String issueKey, FieldDiffs diffs) {
+ IssueChangeDto dto = newDto(issueKey);
+ dto.setChangeType(IssueChangeDto.TYPE_FIELD_CHANGE);
+ dto.setChangeData(diffs.toString());
+ dto.setUserLogin(diffs.userLogin());
+ dto.setIssueChangeCreationDate(diffs.creationDate() == null ? null : diffs.creationDate().getTime());
+ return dto;
+ }
+
+ private static IssueChangeDto newDto(String issueKey) {
+ IssueChangeDto dto = new IssueChangeDto();
+ dto.setIssueKey(issueKey);
+
+ // technical dates - do not use the context date
+ dto.setCreatedAt(System2.INSTANCE.now());
+ dto.setUpdatedAt(System2.INSTANCE.now());
+ return dto;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public IssueChangeDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ @CheckForNull
+ public String getKey() {
+ return kee;
+ }
+
+ public IssueChangeDto setKey(@Nullable String key) {
+ this.kee = key;
+ return this;
+ }
+
+ public String getIssueKey() {
+ return issueKey;
+ }
+
+ public IssueChangeDto setIssueKey(String s) {
+ this.issueKey = s;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public IssueChangeDto setUserLogin(@Nullable String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public String getChangeType() {
+ return changeType;
+ }
+
+ public IssueChangeDto setChangeType(String changeType) {
+ this.changeType = changeType;
+ return this;
+ }
+
+ public String getChangeData() {
+ return changeData;
+ }
+
+ public IssueChangeDto setChangeData(String changeData) {
+ this.changeData = changeData;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ public IssueChangeDto setCreatedAt(Long createdAt) {
+ this.createdAt = checkNotNull(createdAt);
+ return this;
+ }
+
+ public Long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public IssueChangeDto setUpdatedAt(@Nullable Long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public Long getIssueChangeCreationDate() {
+ return issueChangeCreationDate;
+ }
+
+ public IssueChangeDto setIssueChangeCreationDate(@Nullable Long issueChangeCreationDate) {
+ this.issueChangeCreationDate = issueChangeCreationDate;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public DefaultIssueComment toComment() {
+ return new DefaultIssueComment()
+ .setMarkdownText(changeData)
+ .setKey(kee)
+ .setCreatedAt(new Date(createdAt))
+ .setUpdatedAt(updatedAt == null ? null : new Date(updatedAt))
+ .setUserLogin(userLogin)
+ .setIssueKey(issueKey)
+ .setNew(false);
+ }
+
+ public FieldDiffs toFieldDiffs() {
+ return FieldDiffs.parse(changeData)
+ .setUserLogin(userLogin)
+ // issueChangeCreationDate can be null as it has been introduced after createdAt
+ .setCreationDate(issueChangeCreationDate != null ? new Date(issueChangeCreationDate) : new Date(createdAt))
+ .setIssueKey(issueKey);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeMapper.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeMapper.java
new file mode 100644
index 00000000000..1f0efcd0c31
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueChangeMapper.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface IssueChangeMapper {
+
+ void insert(IssueChangeDto dto);
+
+ int delete(String key);
+
+ int update(IssueChangeDto change);
+
+ @CheckForNull
+ IssueChangeDto selectByKeyAndType(@Param("key") String key, @Param("changeType") String type);
+
+ /**
+ * Issue changes by chronological date of creation
+ */
+ List<IssueChangeDto> selectByIssuesAndType(@Param("issueKeys") List<String> issueKeys,
+ @Param("changeType") String changeType);
+
+ List<IssueChangeDto> selectChangelogOfNonClosedIssuesByComponent(@Param("componentUuid") String componentUuid, @Param("changeType") String changeType);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueDao.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueDao.java
new file mode 100644
index 00000000000..edc89f9c267
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueDao.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class IssueDao {
+
+ private final MyBatis mybatis;
+
+ public IssueDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public void selectNonClosedIssuesByModule(long componentId, ResultHandler handler) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ session.select("org.sonar.db.issue.IssueMapper.selectNonClosedIssuesByModule", componentId, handler);
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ protected IssueMapper mapper(DbSession session) {
+ return session.getMapper(IssueMapper.class);
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueDto.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueDto.java
new file mode 100644
index 00000000000..f5eff6e3095
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueDto.java
@@ -0,0 +1,686 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.internal.Uuids;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.rule.RuleDto;
+
+import static org.sonar.api.utils.DateUtils.dateToLong;
+import static org.sonar.api.utils.DateUtils.longToDate;
+
+/**
+ * @since 3.6
+ */
+public final class IssueDto implements Serializable {
+
+ private static final char TAGS_SEPARATOR = ',';
+ private static final Joiner TAGS_JOINER = Joiner.on(TAGS_SEPARATOR).skipNulls();
+ private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
+ private Long id;
+ private String kee;
+ private String componentUuid;
+ private String projectUuid;
+ private Integer ruleId;
+ private String severity;
+ private boolean manualSeverity;
+ private String message;
+ private Integer line;
+ private Double effortToFix;
+ private Long debt;
+ private String status;
+ private String resolution;
+ private String checksum;
+ private String reporter;
+ private String assignee;
+ private String authorLogin;
+ private String actionPlanKey;
+ private String issueAttributes;
+ private long createdAt;
+ private long updatedAt;
+
+ // functional dates stored as Long
+ private Long issueCreationDate;
+ private Long issueUpdateDate;
+ private Long issueCloseDate;
+
+ /**
+ * Temporary date used only during scan
+ */
+ private Long selectedAt;
+
+ // joins
+ private String ruleKey;
+ private String ruleRepo;
+ private String language;
+ private String componentKey;
+ private String moduleUuid;
+ private String moduleUuidPath;
+ private String projectKey;
+ private String filePath;
+ private String tags;
+
+ /**
+ * On batch side, component keys and uuid are useless
+ */
+ public static IssueDto toDtoForComputationInsert(DefaultIssue issue, int ruleId, long now) {
+ return new IssueDto()
+ .setKee(issue.key())
+ .setLine(issue.line())
+ .setMessage(issue.message())
+ .setEffortToFix(issue.effortToFix())
+ .setDebt(issue.debtInMinutes())
+ .setResolution(issue.resolution())
+ .setStatus(issue.status())
+ .setSeverity(issue.severity())
+ .setManualSeverity(issue.manualSeverity())
+ .setChecksum(issue.checksum())
+ .setReporter(issue.reporter())
+ .setAssignee(issue.assignee())
+ .setRuleId(ruleId)
+ .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+ .setTags(issue.tags())
+ .setComponentUuid(issue.componentUuid())
+ .setComponentKey(issue.componentKey())
+ .setModuleUuid(issue.moduleUuid())
+ .setModuleUuidPath(issue.moduleUuidPath())
+ .setComponentUuid(issue.componentUuid())
+ .setProjectUuid(issue.projectUuid())
+ .setProjectKey(issue.projectKey())
+ .setActionPlanKey(issue.actionPlanKey())
+ .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
+ .setAuthorLogin(issue.authorLogin())
+ .setIssueCreationDate(issue.creationDate())
+ .setIssueCloseDate(issue.closeDate())
+ .setIssueUpdateDate(issue.updateDate())
+ .setSelectedAt(issue.selectedAt())
+
+ // technical dates
+ .setCreatedAt(now)
+ .setUpdatedAt(now);
+ }
+
+ /**
+ * On server side, we need component keys and uuid
+ */
+ public static IssueDto toDtoForServerInsert(DefaultIssue issue, ComponentDto component, ComponentDto project, int ruleId, long now) {
+ return toDtoForComputationInsert(issue, ruleId, now)
+ .setComponent(component)
+ .setProject(project);
+ }
+
+ public static IssueDto toDtoForUpdate(DefaultIssue issue, long now) {
+ // Invariant fields, like key and rule, can't be updated
+ return new IssueDto()
+ .setKee(issue.key())
+ .setLine(issue.line())
+ .setMessage(issue.message())
+ .setEffortToFix(issue.effortToFix())
+ .setDebt(issue.debtInMinutes())
+ .setResolution(issue.resolution())
+ .setStatus(issue.status())
+ .setSeverity(issue.severity())
+ .setChecksum(issue.checksum())
+ .setManualSeverity(issue.manualSeverity())
+ .setReporter(issue.reporter())
+ .setAssignee(issue.assignee())
+ .setActionPlanKey(issue.actionPlanKey())
+ .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
+ .setAuthorLogin(issue.authorLogin())
+ .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+ .setTags(issue.tags())
+ .setComponentUuid(issue.componentUuid())
+ .setComponentKey(issue.componentKey())
+ .setModuleUuid(issue.moduleUuid())
+ .setModuleUuidPath(issue.moduleUuidPath())
+ .setProjectUuid(issue.projectUuid())
+ .setProjectKey(issue.projectKey())
+ .setIssueCreationDate(issue.creationDate())
+ .setIssueCloseDate(issue.closeDate())
+ .setIssueUpdateDate(issue.updateDate())
+ .setSelectedAt(issue.selectedAt())
+
+ // technical date
+ .setUpdatedAt(now);
+ }
+
+ public static IssueDto createFor(Project project, RuleDto rule) {
+ return new IssueDto()
+ .setProjectUuid(project.getUuid())
+ .setRuleId(rule.getId())
+ .setKee(Uuids.create());
+ }
+
+ public String getKey() {
+ return getKee();
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public IssueDto setId(@Nullable Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKee() {
+ return kee;
+ }
+
+ public IssueDto setKee(String s) {
+ this.kee = s;
+ return this;
+ }
+
+ public IssueDto setComponent(ComponentDto component) {
+ this.componentKey = component.getKey();
+ this.componentUuid = component.uuid();
+ this.moduleUuid = component.moduleUuid();
+ this.moduleUuidPath = component.moduleUuidPath();
+ this.filePath = component.path();
+ return this;
+ }
+
+ public IssueDto setProject(ComponentDto project) {
+ this.projectKey = project.getKey();
+ this.projectUuid = project.uuid();
+ return this;
+ }
+
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ /**
+ * please use setRule(RuleDto rule)
+ */
+ public IssueDto setRuleId(Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ @CheckForNull
+ public String getActionPlanKey() {
+ return actionPlanKey;
+ }
+
+ public IssueDto setActionPlanKey(@Nullable String s) {
+ this.actionPlanKey = s;
+ return this;
+ }
+
+ @CheckForNull
+ public String getSeverity() {
+ return severity;
+ }
+
+ public IssueDto setSeverity(@Nullable String severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public boolean isManualSeverity() {
+ return manualSeverity;
+ }
+
+ public IssueDto setManualSeverity(boolean manualSeverity) {
+ this.manualSeverity = manualSeverity;
+ return this;
+ }
+
+ @CheckForNull
+ public String getMessage() {
+ return message;
+ }
+
+ public IssueDto setMessage(@Nullable String s) {
+ this.message = s;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getLine() {
+ return line;
+ }
+
+ public IssueDto setLine(@Nullable Integer line) {
+ this.line = line;
+ return this;
+ }
+
+ @CheckForNull
+ public Double getEffortToFix() {
+ return effortToFix;
+ }
+
+ public IssueDto setEffortToFix(@Nullable Double d) {
+ this.effortToFix = d;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getDebt() {
+ return debt;
+ }
+
+ public IssueDto setDebt(@Nullable Long debt) {
+ this.debt = debt;
+ return this;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public IssueDto setStatus(@Nullable String status) {
+ this.status = status;
+ return this;
+ }
+
+ @CheckForNull
+ public String getResolution() {
+ return resolution;
+ }
+
+ public IssueDto setResolution(@Nullable String s) {
+ this.resolution = s;
+ return this;
+ }
+
+ @CheckForNull
+ public String getChecksum() {
+ return checksum;
+ }
+
+ public IssueDto setChecksum(@Nullable String checksum) {
+ this.checksum = checksum;
+ return this;
+ }
+
+ @CheckForNull
+ public String getReporter() {
+ return reporter;
+ }
+
+ public IssueDto setReporter(@Nullable String s) {
+ this.reporter = s;
+ return this;
+ }
+
+ public String getAssignee() {
+ return assignee;
+ }
+
+ public IssueDto setAssignee(@Nullable String s) {
+ this.assignee = s;
+ return this;
+ }
+
+ public String getAuthorLogin() {
+ return authorLogin;
+ }
+
+ public IssueDto setAuthorLogin(@Nullable String authorLogin) {
+ this.authorLogin = authorLogin;
+ return this;
+ }
+
+ public String getIssueAttributes() {
+ return issueAttributes;
+ }
+
+ public IssueDto setIssueAttributes(@Nullable String s) {
+ Preconditions.checkArgument(s == null || s.length() <= 4000,
+ "Issue attributes must not exceed 4000 characters: " + s);
+ this.issueAttributes = s;
+ return this;
+ }
+
+ /**
+ * Technical date
+ */
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ public IssueDto setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ /**
+ * Technical date
+ */
+ public long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public IssueDto setUpdatedAt(long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public Long getIssueCreationTime() {
+ return issueCreationDate;
+ }
+
+ public IssueDto setIssueCreationTime(Long time) {
+ this.issueCreationDate = time;
+ return this;
+ }
+
+ public Date getIssueCreationDate() {
+ return longToDate(issueCreationDate);
+ }
+
+ public IssueDto setIssueCreationDate(@Nullable Date d) {
+ this.issueCreationDate = dateToLong(d);
+ return this;
+ }
+
+ public Long getIssueUpdateTime() {
+ return issueUpdateDate;
+ }
+
+ public IssueDto setIssueUpdateTime(Long time) {
+ this.issueUpdateDate = time;
+ return this;
+ }
+
+ public Date getIssueUpdateDate() {
+ return longToDate(issueUpdateDate);
+ }
+
+ public IssueDto setIssueUpdateDate(@Nullable Date d) {
+ this.issueUpdateDate = dateToLong(d);
+ return this;
+ }
+
+ public Long getIssueCloseTime() {
+ return issueCloseDate;
+ }
+
+ public IssueDto setIssueCloseTime(Long time) {
+ this.issueCloseDate = time;
+ return this;
+ }
+
+ public Date getIssueCloseDate() {
+ return longToDate(issueCloseDate);
+ }
+
+ public IssueDto setIssueCloseDate(@Nullable Date d) {
+ this.issueCloseDate = dateToLong(d);
+ return this;
+ }
+
+ public String getRule() {
+ return ruleKey;
+ }
+
+ public IssueDto setRule(RuleDto rule) {
+ Preconditions.checkNotNull(rule.getId(), "Rule must be persisted.");
+ this.ruleId = rule.getId();
+ this.ruleKey = rule.getRuleKey();
+ this.ruleRepo = rule.getRepositoryKey();
+ this.language = rule.getLanguage();
+ return this;
+ }
+
+ public String getRuleRepo() {
+ return ruleRepo;
+ }
+
+ public RuleKey getRuleKey() {
+ return RuleKey.of(ruleRepo, ruleKey);
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setRule(RuleDto)} instead
+ */
+ public IssueDto setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ public String getComponentKey() {
+ return componentKey;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setComponent(ComponentDto)} instead
+ */
+ public IssueDto setComponentKey(String componentKey) {
+ this.componentKey = componentKey;
+ return this;
+ }
+
+ /**
+ * Can be null on Views or Devs
+ */
+ @CheckForNull
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setComponent(ComponentDto)} instead
+ */
+ public IssueDto setComponentUuid(@Nullable String componentUuid) {
+ this.componentUuid = componentUuid;
+ return this;
+ }
+
+ @CheckForNull
+ public String getModuleUuid() {
+ return moduleUuid;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setComponent(ComponentDto)} instead
+ */
+ public IssueDto setModuleUuid(@Nullable String moduleUuid) {
+ this.moduleUuid = moduleUuid;
+ return this;
+ }
+
+ @CheckForNull
+ public String getModuleUuidPath() {
+ return moduleUuidPath;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setComponent(ComponentDto)} instead
+ */
+ public IssueDto setModuleUuidPath(@Nullable String moduleUuidPath) {
+ this.moduleUuidPath = moduleUuidPath;
+ return this;
+ }
+
+ /**
+ * Used by the issue tracking mechanism, but it should used the component uuid instead
+ */
+ public String getProjectKey() {
+ return projectKey;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setProject(ComponentDto)} instead
+ */
+ public IssueDto setProjectKey(String projectKey) {
+ this.projectKey = projectKey;
+ return this;
+ }
+
+ /**
+ * Can be null on Views or Devs
+ */
+ @CheckForNull
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setProject(ComponentDto)} instead
+ */
+ public IssueDto setProjectUuid(@Nullable String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getSelectedAt() {
+ return selectedAt;
+ }
+
+ public IssueDto setSelectedAt(@Nullable Long d) {
+ this.selectedAt = d;
+ return this;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setRule(RuleDto)} instead
+ */
+ public IssueDto setRuleKey(String repo, String rule) {
+ this.ruleRepo = repo;
+ this.ruleKey = rule;
+ return this;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setProject(ComponentDto)} instead
+ */
+ public String getFilePath() {
+ return filePath;
+ }
+
+ /**
+ * Should only be used to persist in E/S
+ * <p/>
+ * Please use {@link #setProject(ComponentDto)} instead
+ */
+ public IssueDto setFilePath(String filePath) {
+ this.filePath = filePath;
+ return this;
+ }
+
+ public Set<String> getTags() {
+ return ImmutableSet.copyOf(TAGS_SPLITTER.split(tags == null ? "" : tags));
+ }
+
+ public IssueDto setTags(Collection<String> tags) {
+ if (tags.isEmpty()) {
+ this.tags = null;
+ } else {
+ this.tags = TAGS_JOINER.join(tags);
+ }
+ return this;
+ }
+
+ public String getTagsString() {
+ return tags;
+ }
+
+ public IssueDto setTagsString(String tags) {
+ this.tags = tags;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public DefaultIssue toDefaultIssue() {
+ DefaultIssue issue = new DefaultIssue();
+ issue.setKey(kee);
+ issue.setStatus(status);
+ issue.setResolution(resolution);
+ issue.setMessage(message);
+ issue.setEffortToFix(effortToFix);
+ issue.setDebt(debt != null ? Duration.create(debt) : null);
+ issue.setLine(line);
+ issue.setChecksum(checksum);
+ issue.setSeverity(severity);
+ issue.setReporter(reporter);
+ issue.setAssignee(assignee);
+ issue.setAttributes(KeyValueFormat.parse(Objects.firstNonNull(issueAttributes, "")));
+ issue.setComponentKey(componentKey);
+ issue.setComponentUuid(componentUuid);
+ issue.setModuleUuid(moduleUuid);
+ issue.setModuleUuidPath(moduleUuidPath);
+ issue.setProjectUuid(projectUuid);
+ issue.setProjectKey(projectKey);
+ issue.setManualSeverity(manualSeverity);
+ issue.setRuleKey(getRuleKey());
+ issue.setTags(getTags());
+ issue.setLanguage(language);
+ issue.setActionPlanKey(actionPlanKey);
+ issue.setAuthorLogin(authorLogin);
+ issue.setNew(false);
+ issue.setCreationDate(longToDate(issueCreationDate));
+ issue.setCloseDate(longToDate(issueCloseDate));
+ issue.setUpdateDate(longToDate(issueUpdateDate));
+ issue.setSelectedAt(selectedAt);
+ return issue;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDao.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDao.java
new file mode 100644
index 00000000000..6d187866547
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDao.java
@@ -0,0 +1,117 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.MyBatis;
+
+public class IssueFilterDao implements Dao {
+
+ private final MyBatis mybatis;
+
+ public IssueFilterDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ @CheckForNull
+ public IssueFilterDto selectById(long id) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ session.getMapper(IssueFilterMapper.class);
+ return getMapper(session).selectById(id);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<IssueFilterDto> selectByUser(String user) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectByUser(user);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<IssueFilterDto> selectFavoriteFiltersByUser(String user) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectFavoriteFiltersByUser(user);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public IssueFilterDto selectProvidedFilterByName(String name) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectProvidedFilterByName(name);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<IssueFilterDto> selectSharedFilters() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectSharedFilters();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(IssueFilterDto filter) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).insert(filter);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void update(IssueFilterDto filter) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).update(filter);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(long id) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).delete(id);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private IssueFilterMapper getMapper(SqlSession session) {
+ return session.getMapper(IssueFilterMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDto.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDto.java
new file mode 100644
index 00000000000..d3552aae316
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterDto.java
@@ -0,0 +1,116 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.Date;
+import javax.annotation.Nullable;
+
+/**
+ * @since 3.7
+ */
+public class IssueFilterDto {
+
+ private Long id;
+ private String name;
+ private String userLogin;
+ private boolean shared;
+ private String description;
+ private String data;
+ private Date createdAt;
+ private Date updatedAt;
+
+ public IssueFilterDto() {
+ Date now = new Date();
+ createdAt = updatedAt = now;
+ shared = false;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public IssueFilterDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public IssueFilterDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public IssueFilterDto setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public boolean isShared() {
+ return shared;
+ }
+
+ public IssueFilterDto setShared(boolean shared) {
+ this.shared = shared;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public IssueFilterDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public IssueFilterDto setData(String data) {
+ this.data = data;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public IssueFilterDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public IssueFilterDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDao.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDao.java
new file mode 100644
index 00000000000..f9de1a19963
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDao.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+public class IssueFilterFavouriteDao {
+
+ private final MyBatis mybatis;
+
+ public IssueFilterFavouriteDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public IssueFilterFavouriteDto selectById(long id) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectById(id);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<IssueFilterFavouriteDto> selectByFilterId(long filterId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectByFilterId(filterId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(IssueFilterFavouriteDto filter) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).insert(filter);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(long id) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).delete(id);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteByFilterId(long filterId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ getMapper(session).deleteByFilterId(filterId);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private IssueFilterFavouriteMapper getMapper(SqlSession session) {
+ return session.getMapper(IssueFilterFavouriteMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDto.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDto.java
new file mode 100644
index 00000000000..d86bd266bf4
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteDto.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.Date;
+
+/**
+ * @since 3.7
+ */
+public class IssueFilterFavouriteDto {
+
+ private Long id;
+ private String userLogin;
+ private Long issueFilterId;
+ private Date createdAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public IssueFilterFavouriteDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public IssueFilterFavouriteDto setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public Long getIssueFilterId() {
+ return issueFilterId;
+ }
+
+ public IssueFilterFavouriteDto setIssueFilterId(Long issueFilterId) {
+ this.issueFilterId = issueFilterId;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public IssueFilterFavouriteDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteMapper.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteMapper.java
new file mode 100644
index 00000000000..62bd124174b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterFavouriteMapper.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @since 3.7
+ */
+public interface IssueFilterFavouriteMapper {
+
+ @CheckForNull
+ IssueFilterFavouriteDto selectById(long id);
+
+ List<IssueFilterFavouriteDto> selectByFilterId(@Param("filterId") long filterId);
+
+ void insert(IssueFilterFavouriteDto filterFavourite);
+
+ void delete(long id);
+
+ void deleteByFilterId(long filterId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterMapper.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterMapper.java
new file mode 100644
index 00000000000..50c7cc8912d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueFilterMapper.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+
+/**
+ * @since 3.7
+ */
+public interface IssueFilterMapper {
+
+ @CheckForNull
+ IssueFilterDto selectById(long id);
+
+ List<IssueFilterDto> selectByUser(String user);
+
+ List<IssueFilterDto> selectFavoriteFiltersByUser(String user);
+
+ List<IssueFilterDto> selectSharedFilters();
+
+ IssueFilterDto selectProvidedFilterByName(String name);
+
+ void insert(IssueFilterDto filter);
+
+ void update(IssueFilterDto filter);
+
+ void delete(long id);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/IssueMapper.java b/sonar-db/src/main/java/org/sonar/db/issue/IssueMapper.java
new file mode 100644
index 00000000000..eda8b2ef7ba
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/IssueMapper.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.List;
+import java.util.Set;
+
+public interface IssueMapper {
+
+ IssueDto selectByKey(String key);
+
+ List<IssueDto> selectNonClosedByComponentUuid(String componentUuid);
+
+ Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid);
+
+ List<IssueDto> selectByKeys(List<String> keys);
+
+ List<IssueDto> selectByActionPlan(String actionPlan);
+
+ void insert(IssueDto issue);
+
+ int update(IssueDto issue);
+
+ int updateIfBeforeSelectedDate(IssueDto issue);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/issue/package-info.java b/sonar-db/src/main/java/org/sonar/db/issue/package-info.java
new file mode 100644
index 00000000000..472eca27cae
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/issue/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.issue;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDao.java b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDao.java
new file mode 100644
index 00000000000..ccdb5c45101
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDao.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.loadedtemplate;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class LoadedTemplateDao implements Dao {
+
+ private MyBatis mybatis;
+
+ public LoadedTemplateDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public int countByTypeAndKey(String type, String key) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return countByTypeAndKey(type, key, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public int countByTypeAndKey(String type, String key, SqlSession session) {
+ return session.getMapper(LoadedTemplateMapper.class).countByTypeAndKey(type, key);
+ }
+
+ public void insert(LoadedTemplateDto loadedTemplateDto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ insert(loadedTemplateDto, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(LoadedTemplateDto loadedTemplateDto, SqlSession session) {
+ session.getMapper(LoadedTemplateMapper.class).insert(loadedTemplateDto);
+ }
+
+ public void delete(DbSession session, String type, String key) {
+ session.getMapper(LoadedTemplateMapper.class).delete(type, key);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java
new file mode 100644
index 00000000000..36300281c96
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateDto.java
@@ -0,0 +1,89 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.loadedtemplate;
+
+import com.google.common.base.Objects;
+
+public final class LoadedTemplateDto {
+
+ public static final String DASHBOARD_TYPE = "DASHBOARD";
+ public static final String FILTER_TYPE = "FILTER";
+ public static final String QUALITY_PROFILE_TYPE = "QUALITY_PROFILE";
+ public static final String PERMISSION_TEMPLATE_TYPE = "PERM_TEMPLATE";
+ public static final String QUALITY_GATE_TYPE = "QUALITY_GATE";
+ public static final String ONE_SHOT_TASK_TYPE = "ONE_SHOT_TASK";
+ public static final String ISSUE_FILTER_TYPE = "ISSUE_FILTER";
+
+ private Long id;
+ private String key;
+ private String type;
+
+ public LoadedTemplateDto() {
+ }
+
+ public LoadedTemplateDto(String key, String type) {
+ this.key = key;
+ this.type = type;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public LoadedTemplateDto setId(Long l) {
+ this.id = l;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public LoadedTemplateDto setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public LoadedTemplateDto setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LoadedTemplateDto other = (LoadedTemplateDto) o;
+ return Objects.equal(id, other.id) && Objects.equal(key, other.key) && Objects.equal(type, other.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : 0;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateMapper.java b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateMapper.java
new file mode 100644
index 00000000000..af8ace065bd
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/LoadedTemplateMapper.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.loadedtemplate;
+
+import org.apache.ibatis.annotations.Param;
+
+public interface LoadedTemplateMapper {
+
+ int countByTypeAndKey(@Param("type") String type, @Param("key") String key);
+
+ void insert(LoadedTemplateDto template);
+
+ void delete(@Param("type") String type, @Param("key") String key);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/loadedtemplate/package-info.java b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/package-info.java
new file mode 100644
index 00000000000..2dff941c724
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/loadedtemplate/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.loadedtemplate;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureDto.java b/sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureDto.java
new file mode 100644
index 00000000000..e7387d34cb9
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureDto.java
@@ -0,0 +1,114 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+public class CustomMeasureDto {
+ private long id;
+ private int metricId;
+ private String componentUuid;
+ private double value;
+ private String textValue;
+ private String userLogin;
+ private String description;
+ private long createdAt;
+ private long updatedAt;
+
+ public String getDescription() {
+ return description;
+ }
+
+ public CustomMeasureDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public CustomMeasureDto setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public String getTextValue() {
+ return textValue;
+ }
+
+ public CustomMeasureDto setTextValue(String textValue) {
+ this.textValue = textValue;
+ return this;
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public CustomMeasureDto setValue(double value) {
+ this.value = value;
+ return this;
+ }
+
+ public int getMetricId() {
+ return metricId;
+ }
+
+ public CustomMeasureDto setMetricId(int metricId) {
+ this.metricId = metricId;
+ return this;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public CustomMeasureDto setId(long id) {
+ this.id = id;
+ return this;
+ }
+
+ public long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public CustomMeasureDto setUpdatedAt(long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ public CustomMeasureDto setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ public CustomMeasureDto setComponentUuid(String componentUuid) {
+ this.componentUuid = componentUuid;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureMapper.java b/sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureMapper.java
new file mode 100644
index 00000000000..e5f46294633
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/CustomMeasureMapper.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.RowBounds;
+
+public interface CustomMeasureMapper {
+ void insert(CustomMeasureDto customMeasure);
+
+ void update(CustomMeasureDto customMeasure);
+
+ void delete(long id);
+
+ void deleteByMetricIds(@Param("metricIds") List<Integer> metricIds);
+
+ CustomMeasureDto selectById(long id);
+
+ List<CustomMeasureDto> selectByMetricId(int id);
+
+ List<CustomMeasureDto> selectByComponentUuid(String s);
+
+ List<CustomMeasureDto> selectByComponentUuid(String s, RowBounds rowBounds);
+
+ int countByComponentUuid(String componentUuid);
+
+ int countByComponentIdAndMetricId(@Param("componentUuid") String componentUuid, @Param("metricId") int metricId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureDto.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureDto.java
new file mode 100644
index 00000000000..0ba16182f0e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureDto.java
@@ -0,0 +1,268 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import com.google.common.base.Objects;
+import java.nio.charset.StandardCharsets;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class MeasureDto {
+ private static final String INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5 = "Index should be in range from 1 to 5";
+ private static final int MAX_TEXT_VALUE_LENGTH = 4000;
+
+ private Long id;
+ private Double value;
+ private String textValue;
+ private byte[] dataValue;
+ private Double variation1;
+ private Double variation2;
+ private Double variation3;
+ private Double variation4;
+ private Double variation5;
+ private String alertStatus;
+ private String alertText;
+ private String description;
+
+ private Long componentId;
+ private Long snapshotId;
+ private Integer metricId;
+ private Integer ruleId;
+ private Integer characteristicId;
+ private Integer personId;
+
+ // TODO to delete – not in db
+ private String metricKey;
+ private String componentKey;
+
+ public Long getId() {
+ return id;
+ }
+
+ public MeasureDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ @CheckForNull
+ public Double getValue() {
+ return value;
+ }
+
+ public MeasureDto setValue(@Nullable Double value) {
+ this.value = value;
+ return this;
+ }
+
+ @CheckForNull
+ public String getData() {
+ if (dataValue != null) {
+ return new String(dataValue, StandardCharsets.UTF_8);
+ }
+ return textValue;
+ }
+
+ public MeasureDto setData(@Nullable String data) {
+ if (data == null) {
+ this.textValue = null;
+ this.dataValue = null;
+ } else if (data.length() > MAX_TEXT_VALUE_LENGTH) {
+ this.textValue = null;
+ this.dataValue = data.getBytes(StandardCharsets.UTF_8);
+ } else {
+ this.textValue = data;
+ this.dataValue = null;
+ }
+
+ return this;
+ }
+
+ @CheckForNull
+ public Double getVariation(int index) {
+ switch (index) {
+ case 1:
+ return variation1;
+ case 2:
+ return variation2;
+ case 3:
+ return variation3;
+ case 4:
+ return variation4;
+ case 5:
+ return variation5;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ }
+
+ public MeasureDto setVariation(int index, @Nullable Double d) {
+ switch (index) {
+ case 1:
+ variation1 = d;
+ break;
+ case 2:
+ variation2 = d;
+ break;
+ case 3:
+ variation3 = d;
+ break;
+ case 4:
+ variation4 = d;
+ break;
+ case 5:
+ variation5 = d;
+ break;
+ default:
+ throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
+ }
+ return this;
+ }
+
+ @CheckForNull
+ public String getAlertStatus() {
+ return alertStatus;
+ }
+
+ public MeasureDto setAlertStatus(@Nullable String alertStatus) {
+ this.alertStatus = alertStatus;
+ return this;
+ }
+
+ @CheckForNull
+ public String getAlertText() {
+ return alertText;
+ }
+
+ public MeasureDto setAlertText(@Nullable String alertText) {
+ this.alertText = alertText;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public MeasureDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Long getComponentId() {
+ return componentId;
+ }
+
+ public MeasureDto setComponentId(Long componentId) {
+ this.componentId = componentId;
+ return this;
+ }
+
+ public Integer getMetricId() {
+ return metricId;
+ }
+
+ public MeasureDto setMetricId(Integer metricId) {
+ this.metricId = metricId;
+ return this;
+ }
+
+ public Long getSnapshotId() {
+ return snapshotId;
+ }
+
+ public MeasureDto setSnapshotId(Long snapshotId) {
+ this.snapshotId = snapshotId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ public MeasureDto setRuleId(@Nullable Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ public Integer getCharacteristicId() {
+ return characteristicId;
+ }
+
+ public MeasureDto setCharacteristicId(@Nullable Integer characteristicId) {
+ this.characteristicId = characteristicId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getPersonId() {
+ return personId;
+ }
+
+ public MeasureDto setPersonId(@Nullable Integer personId) {
+ this.personId = personId;
+ return this;
+ }
+
+ public String getMetricKey() {
+ return metricKey;
+ }
+
+ public MeasureDto setMetricKey(String metricKey) {
+ this.metricKey = metricKey;
+ return this;
+ }
+
+ public String getComponentKey() {
+ return componentKey;
+ }
+
+ public MeasureDto setComponentKey(String componentKey) {
+ this.componentKey = componentKey;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("id", id)
+ .add("value", value)
+ .add("textValue", textValue)
+ .add("dataValue", dataValue)
+ .add("variation1", variation1)
+ .add("variation2", variation2)
+ .add("variation3", variation3)
+ .add("variation4", variation4)
+ .add("variation5", variation5)
+ .add("alertStatus", alertStatus)
+ .add("alertText", alertText)
+ .add("description", description)
+ .add("componentId", componentId)
+ .add("snapshotId", snapshotId)
+ .add("metricId", metricId)
+ .add("ruleId", ruleId)
+ .add("characteristicId", characteristicId)
+ .add("personId", personId)
+ .add("metricKey", metricKey)
+ .add("componentKey", componentKey)
+ .toString();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDao.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDao.java
new file mode 100644
index 00000000000..50d0bad2f53
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDao.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.measure;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+public class MeasureFilterDao {
+ private MyBatis mybatis;
+
+ public MeasureFilterDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public MeasureFilterDto findSystemFilterByName(String name) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ MeasureFilterMapper mapper = session.getMapper(MeasureFilterMapper.class);
+ return mapper.findSystemFilterByName(name);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(MeasureFilterDto filter) {
+ SqlSession session = mybatis.openSession(false);
+ MeasureFilterMapper mapper = session.getMapper(MeasureFilterMapper.class);
+ try {
+ mapper.insert(filter);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDto.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDto.java
new file mode 100644
index 00000000000..f693d815790
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterDto.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.measure;
+
+import java.util.Date;
+import javax.annotation.Nullable;
+
+/**
+ * @since 3.4
+ */
+public class MeasureFilterDto {
+ private Long id;
+ private String name;
+ private Long userId;
+ private Boolean shared;
+ private String description;
+ private String data;
+ private Date createdAt;
+ private Date updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public MeasureFilterDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public MeasureFilterDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public MeasureFilterDto setUserId(@Nullable Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public Boolean isShared() {
+ return shared;
+ }
+
+ public MeasureFilterDto setShared(@Nullable Boolean shared) {
+ this.shared = shared;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public MeasureFilterDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public MeasureFilterDto setData(String data) {
+ this.data = data;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public MeasureFilterDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public MeasureFilterDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterMapper.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterMapper.java
new file mode 100644
index 00000000000..ed0c51d3dc2
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureFilterMapper.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.measure;
+
+/**
+ * @since 3.4
+ */
+public interface MeasureFilterMapper {
+ MeasureFilterDto findSystemFilterByName(String name);
+
+ void insert(MeasureFilterDto filter);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java b/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java
new file mode 100644
index 00000000000..8abcea4d7cf
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/MeasureMapper.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface MeasureMapper {
+
+ MeasureDto selectByKey(@Param("componentKey") String componentKey, @Param("metricKey") String metricKey);
+
+ List<MeasureDto> selectByComponentAndMetrics(@Param("componentKey") String componentKey, @Param("metricKeys") List<String> metricKeys);
+
+ @CheckForNull
+ MeasureDto selectByComponentAndMetric(@Param("componentKey") String componentKey, @Param("metricKey") String metricKey);
+
+ List<PastMeasureDto> selectByComponentUuidAndProjectSnapshotIdAndStatusAndMetricIds(@Param("componentUuid") String componentuuid, @Param("rootSnapshotId") long rootSnapshotId,
+ @Param("metricIds") List<Integer> metricIds, @Param("status") String status);
+
+ long countByComponentAndMetric(@Param("componentKey") String componentKey, @Param("metricKey") String metricKey);
+
+ void insert(MeasureDto measureDto);
+
+ List<String> selectMetricKeysForSnapshot(@Param("snapshotId") long snapshotId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/PastMeasureDto.java b/sonar-db/src/main/java/org/sonar/db/measure/PastMeasureDto.java
new file mode 100644
index 00000000000..f82099b6e03
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/PastMeasureDto.java
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class PastMeasureDto {
+
+ private Long id;
+ private Double value;
+ private Integer metricId;
+ private Integer ruleId;
+ private Integer characteristicId;
+ private Integer personId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public PastMeasureDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public double getValue() {
+ Objects.requireNonNull(value);
+ return value;
+ }
+
+ public PastMeasureDto setValue(@Nullable Double value) {
+ this.value = value;
+ return this;
+ }
+
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ public Integer getMetricId() {
+ return metricId;
+ }
+
+ public PastMeasureDto setMetricId(Integer metricId) {
+ this.metricId = metricId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getCharacteristicId() {
+ return characteristicId;
+ }
+
+ public PastMeasureDto setCharacteristicId(@Nullable Integer characteristicId) {
+ this.characteristicId = characteristicId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getPersonId() {
+ return personId;
+ }
+
+ public PastMeasureDto setPersonId(@Nullable Integer personId) {
+ this.personId = personId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ public PastMeasureDto setRuleId(@Nullable Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/package-info.java b/sonar-db/src/main/java/org/sonar/db/measure/package-info.java
new file mode 100644
index 00000000000..4485a31899b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/measure/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.measure;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/metric/MetricDto.java b/sonar-db/src/main/java/org/sonar/db/metric/MetricDto.java
new file mode 100644
index 00000000000..9822dabaa84
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/metric/MetricDto.java
@@ -0,0 +1,199 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.metric;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class MetricDto {
+
+ private Integer id;
+
+ private String kee;
+
+ private String shortName;
+
+ private String valueType;
+
+ private String description;
+
+ private String domain;
+
+ private int direction;
+
+ private boolean qualitative;
+
+ private boolean userManaged;
+
+ private Double worstValue;
+
+ private Double bestValue;
+
+ private boolean optimizedBestValue;
+
+ private boolean hidden;
+
+ private boolean deleteHistoricalData;
+
+ private boolean enabled;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public MetricDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey() {
+ return kee;
+ }
+
+ public MetricDto setKey(String name) {
+ this.kee = name;
+ return this;
+ }
+
+ public String getShortName() {
+ return shortName;
+ }
+
+ public MetricDto setShortName(String shortName) {
+ this.shortName = shortName;
+ return this;
+ }
+
+ public String getValueType() {
+ return valueType;
+ }
+
+ public MetricDto setValueType(String valueType) {
+ this.valueType = valueType;
+ return this;
+ }
+
+ /**
+ * @return null for manual metrics
+ */
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public MetricDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public MetricDto setDomain(String domain) {
+ this.domain = domain;
+ return this;
+ }
+
+ public int getDirection() {
+ return direction;
+ }
+
+ public MetricDto setDirection(int direction) {
+ this.direction = direction;
+ return this;
+ }
+
+ public boolean isQualitative() {
+ return qualitative;
+ }
+
+ public MetricDto setQualitative(boolean qualitative) {
+ this.qualitative = qualitative;
+ return this;
+ }
+
+ public boolean isUserManaged() {
+ return userManaged;
+ }
+
+ public MetricDto setUserManaged(boolean userManaged) {
+ this.userManaged = userManaged;
+ return this;
+ }
+
+ @CheckForNull
+ public Double getWorstValue() {
+ return worstValue;
+ }
+
+ public MetricDto setWorstValue(@Nullable Double worstValue) {
+ this.worstValue = worstValue;
+ return this;
+ }
+
+ @CheckForNull
+ public Double getBestValue() {
+ return bestValue;
+ }
+
+ public MetricDto setBestValue(@Nullable Double bestValue) {
+ this.bestValue = bestValue;
+ return this;
+ }
+
+ public boolean isOptimizedBestValue() {
+ return optimizedBestValue;
+ }
+
+ public MetricDto setOptimizedBestValue(boolean optimizedBestValue) {
+ this.optimizedBestValue = optimizedBestValue;
+ return this;
+ }
+
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ public MetricDto setHidden(boolean hidden) {
+ this.hidden = hidden;
+ return this;
+ }
+
+ public boolean isDeleteHistoricalData() {
+ return deleteHistoricalData;
+ }
+
+ public MetricDto setDeleteHistoricalData(boolean deleteHistoricalData) {
+ this.deleteHistoricalData = deleteHistoricalData;
+ return this;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public MetricDto setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/metric/MetricMapper.java b/sonar-db/src/main/java/org/sonar/db/metric/MetricMapper.java
new file mode 100644
index 00000000000..68ff2ccf0b4
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/metric/MetricMapper.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.metric;
+
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.RowBounds;
+
+public interface MetricMapper {
+
+ MetricDto selectById(long id);
+
+ List<MetricDto> selectByIds(@Param("ids") List<Integer> ids);
+
+ MetricDto selectByKey(@Param("key") String key);
+
+ List<MetricDto> selectByKeys(@Param("keys") List<String> keys);
+
+ List<MetricDto> selectAllEnabled();
+
+ List<MetricDto> selectAllEnabled(Map<String, Object> properties, RowBounds rowBounds);
+
+ void insert(MetricDto dto);
+
+ List<String> selectDomains();
+
+ void disableByIds(@Param("ids") List<Integer> ids);
+
+ void disableByKey(@Param("key") String key);
+
+ int countEnabled(@Param("isCustom") @Nullable Boolean isCustom);
+
+ void update(MetricDto metric);
+
+ List<MetricDto> selectAvailableCustomMetricsByComponentUuid(String projectUuid);
+
+ List<MetricDto> selectAvailableCustomMetricsByComponentKey(String projectKey);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/metric/package-info.java b/sonar-db/src/main/java/org/sonar/db/metric/package-info.java
new file mode 100644
index 00000000000..b0c6b63660b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/metric/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.metric;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDao.java b/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDao.java
new file mode 100644
index 00000000000..b82ba01dd09
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDao.java
@@ -0,0 +1,83 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.notification;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class NotificationQueueDao {
+
+ private final MyBatis mybatis;
+
+ public NotificationQueueDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public void insert(List<NotificationQueueDto> dtos) {
+ DbSession session = mybatis.openSession(true);
+ NotificationQueueMapper mapper = session.getMapper(NotificationQueueMapper.class);
+ try {
+ for (NotificationQueueDto dto : dtos) {
+ mapper.insert(dto);
+ }
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(List<NotificationQueueDto> dtos) {
+ DbSession session = mybatis.openSession(true);
+ NotificationQueueMapper mapper = session.getMapper(NotificationQueueMapper.class);
+ try {
+ for (NotificationQueueDto dto : dtos) {
+ mapper.delete(dto.getId());
+ }
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<NotificationQueueDto> findOldest(int count) {
+ if (count < 1) {
+ return Collections.emptyList();
+ }
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(NotificationQueueMapper.class).findOldest(count);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public long count() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(NotificationQueueMapper.class).count();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDto.java b/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDto.java
new file mode 100644
index 00000000000..44a5602bb64
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueDto.java
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.notification;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.utils.SonarException;
+
+/**
+ * @since 3.7.1
+ */
+public class NotificationQueueDto {
+
+ private Long id;
+ private byte[] data;
+
+ public Long getId() {
+ return id;
+ }
+
+ public NotificationQueueDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public NotificationQueueDto setData(byte[] data) {
+ this.data = data;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public static NotificationQueueDto toNotificationQueueDto(Notification notification) {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ objectOutputStream.writeObject(notification);
+ objectOutputStream.close();
+ return new NotificationQueueDto().setData(byteArrayOutputStream.toByteArray());
+
+ } catch (IOException e) {
+ throw new SonarException("Unable to write notification", e);
+
+ } finally {
+ IOUtils.closeQuietly(byteArrayOutputStream);
+ }
+ }
+
+ public Notification toNotification() throws IOException, ClassNotFoundException {
+ if (this.data == null) {
+ return null;
+ }
+ ByteArrayInputStream byteArrayInputStream = null;
+ try {
+ byteArrayInputStream = new ByteArrayInputStream(this.data);
+ ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
+ Object result = objectInputStream.readObject();
+ objectInputStream.close();
+ return (Notification) result;
+ } finally {
+ IOUtils.closeQuietly(byteArrayInputStream);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueMapper.java b/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueMapper.java
new file mode 100644
index 00000000000..dead94fc4ea
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/notification/NotificationQueueMapper.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.notification;
+
+import java.util.List;
+
+/**
+ * @since 3.7.1
+ */
+public interface NotificationQueueMapper {
+
+ void insert(NotificationQueueDto actionPlanDto);
+
+ void delete(long id);
+
+ List<NotificationQueueDto> findOldest(int count);
+
+ long count();
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/notification/package-info.java b/sonar-db/src/main/java/org/sonar/db/notification/package-info.java
new file mode 100644
index 00000000000..a6d74836ec6
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/notification/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.notification;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/package-info.java b/sonar-db/src/main/java/org/sonar/db/package-info.java
new file mode 100644
index 00000000000..d2f73de8eb1
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/GroupWithPermissionDto.java b/sonar-db/src/main/java/org/sonar/db/permission/GroupWithPermissionDto.java
new file mode 100644
index 00000000000..2dbcd04203c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/GroupWithPermissionDto.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.core.permission.GroupWithPermission;
+
+public class GroupWithPermissionDto {
+
+ private String name;
+ private String permission;
+ private String description;
+
+ public String getName() {
+ return name;
+ }
+
+ public GroupWithPermissionDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPermission() {
+ return permission;
+ }
+
+ public GroupWithPermissionDto setPermission(@Nullable String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public GroupWithPermissionDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public GroupWithPermission toGroupWithPermission() {
+ return new GroupWithPermission()
+ .setName(name)
+ .setDescription(description)
+ .hasPermission(permission != null);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java
new file mode 100644
index 00000000000..e6a1508e222
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java
@@ -0,0 +1,91 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Maps.newHashMap;
+
+@ServerSide
+public class PermissionDao {
+
+ private static final String QUERY_PARAMETER = "query";
+ private static final String COMPONENT_ID_PARAMETER = "componentId";
+
+ private final MyBatis myBatis;
+
+ public PermissionDao(MyBatis myBatis) {
+ this.myBatis = myBatis;
+ }
+
+ /**
+ * @return a paginated list of users.
+ */
+ public List<UserWithPermissionDto> selectUsers(PermissionQuery query, @Nullable Long componentId, int offset, int limit) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ Map<String, Object> params = newHashMap();
+ params.put(QUERY_PARAMETER, query);
+ params.put(COMPONENT_ID_PARAMETER, componentId);
+
+ return mapper(session).selectUsers(params, new RowBounds(offset, limit));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @VisibleForTesting
+ List<UserWithPermissionDto> selectUsers(PermissionQuery query, @Nullable Long componentId) {
+ return selectUsers(query, componentId, 0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * 'Anyone' group is not returned when it has not the asked permission.
+ * Membership parameter from query is not taking into account in order to deal more easily with the 'Anyone' group
+ * @return a non paginated list of groups.
+ */
+ public List<GroupWithPermissionDto> selectGroups(PermissionQuery query, @Nullable Long componentId) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ Map<String, Object> params = newHashMap();
+ params.put(QUERY_PARAMETER, query);
+ params.put(COMPONENT_ID_PARAMETER, componentId);
+ params.put("anyoneGroup", DefaultGroups.ANYONE);
+
+ return mapper(session).selectGroups(params);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private PermissionMapper mapper(SqlSession session) {
+ return session.getMapper(PermissionMapper.class);
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionFacade.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionFacade.java
new file mode 100644
index 00000000000..116e2e2634f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionFacade.java
@@ -0,0 +1,248 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.config.Settings;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.GroupRoleDto;
+import org.sonar.db.user.RoleDao;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserRoleDto;
+
+/**
+ * This facade wraps db operations related to permissions
+ *
+ * Should be removed when batch will no more create permission, and be replaced by a new PermissionService in module server (probably be a merge with InternalPermissionService)
+ *
+ * WARNING, this class is called by Views to apply default permission template on new views
+ */
+@ServerSide
+public class PermissionFacade {
+
+ private final RoleDao roleDao;
+ private final UserDao userDao;
+ private final PermissionTemplateDao permissionTemplateDao;
+ private final Settings settings;
+ private final ResourceDao resourceDao;
+
+ public PermissionFacade(RoleDao roleDao, UserDao userDao, ResourceDao resourceDao, PermissionTemplateDao permissionTemplateDao, Settings settings) {
+ this.roleDao = roleDao;
+ this.userDao = userDao;
+ this.resourceDao = resourceDao;
+ this.permissionTemplateDao = permissionTemplateDao;
+ this.settings = settings;
+ }
+
+ /**
+ * @param updateProjectAuthorizationDate is false when doing bulk action in order to not update the same project multiple times for nothing
+ */
+ private void insertUserPermission(@Nullable Long resourceId, Long userId, String permission, boolean updateProjectAuthorizationDate, DbSession session) {
+ UserRoleDto userRoleDto = new UserRoleDto()
+ .setRole(permission)
+ .setUserId(userId)
+ .setResourceId(resourceId);
+ if (updateProjectAuthorizationDate) {
+ updateProjectAuthorizationDate(resourceId, session);
+ }
+ roleDao.insertUserRole(userRoleDto, session);
+ }
+
+ public void insertUserPermission(@Nullable Long resourceId, Long userId, String permission, DbSession session) {
+ insertUserPermission(resourceId, userId, permission, true, session);
+ }
+
+ public void deleteUserPermission(@Nullable Long resourceId, Long userId, String permission, DbSession session) {
+ UserRoleDto userRoleDto = new UserRoleDto()
+ .setRole(permission)
+ .setUserId(userId)
+ .setResourceId(resourceId);
+ updateProjectAuthorizationDate(resourceId, session);
+ roleDao.deleteUserRole(userRoleDto, session);
+ }
+
+ private void insertGroupPermission(@Nullable Long resourceId, @Nullable Long groupId, String permission, boolean updateProjectAuthorizationDate, DbSession session) {
+ GroupRoleDto groupRole = new GroupRoleDto()
+ .setRole(permission)
+ .setGroupId(groupId)
+ .setResourceId(resourceId);
+ updateProjectAuthorizationDate(resourceId, session);
+ roleDao.insertGroupRole(groupRole, session);
+ }
+
+ public void insertGroupPermission(@Nullable Long resourceId, @Nullable Long groupId, String permission, DbSession session) {
+ insertGroupPermission(resourceId, groupId, permission, true, session);
+ }
+
+ public void insertGroupPermission(@Nullable Long resourceId, String groupName, String permission, DbSession session) {
+ if (DefaultGroups.isAnyone(groupName)) {
+ insertGroupPermission(resourceId, (Long) null, permission, session);
+ } else {
+ GroupDto group = userDao.selectGroupByName(groupName, session);
+ if (group != null) {
+ insertGroupPermission(resourceId, group.getId(), permission, session);
+ }
+ }
+ }
+
+ public void deleteGroupPermission(@Nullable Long resourceId, @Nullable Long groupId, String permission, DbSession session) {
+ GroupRoleDto groupRole = new GroupRoleDto()
+ .setRole(permission)
+ .setGroupId(groupId)
+ .setResourceId(resourceId);
+ updateProjectAuthorizationDate(resourceId, session);
+ roleDao.deleteGroupRole(groupRole, session);
+ }
+
+ public void deleteGroupPermission(@Nullable Long resourceId, String groupName, String permission, DbSession session) {
+ if (DefaultGroups.isAnyone(groupName)) {
+ deleteGroupPermission(resourceId, (Long) null, permission, session);
+ } else {
+ GroupDto group = userDao.selectGroupByName(groupName, session);
+ if (group != null) {
+ deleteGroupPermission(resourceId, group.getId(), permission, session);
+ }
+ }
+ }
+
+ /**
+ * For each modification of permission on a project, update the authorization_updated_at to help ES reindex only relevant changes
+ */
+ private void updateProjectAuthorizationDate(@Nullable Long projectId, DbSession session) {
+ if (projectId != null) {
+ resourceDao.updateAuthorizationDate(projectId, session);
+ }
+ }
+
+ /**
+ * Load permission template and load associated collections of users and groups permissions
+ */
+ @VisibleForTesting
+ PermissionTemplateDto getPermissionTemplateWithPermissions(DbSession session, String templateKey) {
+ PermissionTemplateDto permissionTemplateDto = permissionTemplateDao.selectTemplateByKey(session, templateKey);
+ if (permissionTemplateDto == null) {
+ throw new IllegalArgumentException("Could not retrieve permission template with key " + templateKey);
+ }
+ PermissionTemplateDto templateWithPermissions = permissionTemplateDao.selectPermissionTemplate(session, permissionTemplateDto.getKee());
+ if (templateWithPermissions == null) {
+ throw new IllegalArgumentException("Could not retrieve permissions for template with key " + templateKey);
+ }
+ return templateWithPermissions;
+ }
+
+ public void applyPermissionTemplate(DbSession session, String templateKey, Long resourceId) {
+ PermissionTemplateDto permissionTemplate = getPermissionTemplateWithPermissions(session, templateKey);
+ updateProjectAuthorizationDate(resourceId, session);
+ removeAllPermissions(resourceId, session);
+ List<PermissionTemplateUserDto> usersPermissions = permissionTemplate.getUsersPermissions();
+ if (usersPermissions != null) {
+ for (PermissionTemplateUserDto userPermission : usersPermissions) {
+ insertUserPermission(resourceId, userPermission.getUserId(), userPermission.getPermission(), false, session);
+ }
+ }
+ List<PermissionTemplateGroupDto> groupsPermissions = permissionTemplate.getGroupsPermissions();
+ if (groupsPermissions != null) {
+ for (PermissionTemplateGroupDto groupPermission : groupsPermissions) {
+ Long groupId = groupPermission.getGroupId() == null ? null : groupPermission.getGroupId();
+ insertGroupPermission(resourceId, groupId, groupPermission.getPermission(), false, session);
+ }
+ }
+ }
+
+ public int countComponentPermissions(DbSession session, Long resourceId) {
+ return roleDao.countResourceGroupRoles(session, resourceId) + roleDao.countResourceUserRoles(session, resourceId);
+ }
+
+ protected void removeAllPermissions(Long resourceId, DbSession session) {
+ roleDao.deleteGroupRolesByResourceId(resourceId, session);
+ roleDao.deleteUserRolesByResourceId(resourceId, session);
+ }
+
+ public List<String> selectGroupPermissions(DbSession session, String group, @Nullable Long componentId) {
+ return roleDao.selectGroupPermissions(session, group, componentId);
+ }
+
+ public List<String> selectUserPermissions(DbSession session, String user, @Nullable Long componentId) {
+ return roleDao.selectUserPermissions(session, user, componentId);
+ }
+
+ public void grantDefaultRoles(DbSession session, Long componentId, String qualifier) {
+ ResourceDto resource = resourceDao.getResource(componentId, session);
+ String applicablePermissionTemplateKey = getApplicablePermissionTemplateKey(session, resource.getKey(), qualifier);
+ applyPermissionTemplate(session, applicablePermissionTemplateKey, componentId);
+ }
+
+ /**
+ * Return the permission template for the given componentKey. If no template key pattern match then consider default
+ * permission template for the resource qualifier.
+ */
+ private String getApplicablePermissionTemplateKey(DbSession session, final String componentKey, String qualifier) {
+ List<PermissionTemplateDto> allPermissionTemplates = permissionTemplateDao.selectAllPermissionTemplates(session);
+ List<PermissionTemplateDto> matchingTemplates = new ArrayList<>();
+ for (PermissionTemplateDto permissionTemplateDto : allPermissionTemplates) {
+ String keyPattern = permissionTemplateDto.getKeyPattern();
+ if (StringUtils.isNotBlank(keyPattern) && componentKey.matches(keyPattern)) {
+ matchingTemplates.add(permissionTemplateDto);
+ }
+ }
+ checkAtMostOneMatchForComponentKey(componentKey, matchingTemplates);
+ if (matchingTemplates.size() == 1) {
+ return matchingTemplates.get(0).getKee();
+ }
+ String qualifierTemplateKey = settings.getString("sonar.permission.template." + qualifier + ".default");
+ if (!StringUtils.isBlank(qualifierTemplateKey)) {
+ return qualifierTemplateKey;
+ }
+
+ String defaultTemplateKey = settings.getString("sonar.permission.template.default");
+ if (StringUtils.isBlank(defaultTemplateKey)) {
+ throw new IllegalStateException("At least one default permission template should be defined");
+ }
+ return defaultTemplateKey;
+ }
+
+ private void checkAtMostOneMatchForComponentKey(final String componentKey, List<PermissionTemplateDto> matchingTemplates) {
+ if (matchingTemplates.size() > 1) {
+ StringBuilder templatesNames = new StringBuilder();
+ for (Iterator<PermissionTemplateDto> it = matchingTemplates.iterator(); it.hasNext();) {
+ templatesNames.append("\"").append(it.next().getName()).append("\"");
+ if (it.hasNext()) {
+ templatesNames.append(", ");
+ }
+ }
+ throw new IllegalStateException(MessageFormat.format(
+ "The \"{0}\" key matches multiple permission templates: {1}."
+ + " A system administrator must update these templates so that only one of them matches the key.", componentKey,
+ templatesNames.toString()));
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java
new file mode 100644
index 00000000000..08c7b3e07c0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.session.RowBounds;
+
+public interface PermissionMapper {
+
+ List<UserWithPermissionDto> selectUsers(Map<String, Object> parameters, RowBounds rowBounds);
+
+ List<GroupWithPermissionDto> selectGroups(Map<String, Object> parameters);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionQuery.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionQuery.java
new file mode 100644
index 00000000000..6d1a04a66bb
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionQuery.java
@@ -0,0 +1,199 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Query used to get users and groups from a permission
+ */
+public class PermissionQuery {
+
+ public static final int DEFAULT_PAGE_INDEX = 1;
+ public static final int DEFAULT_PAGE_SIZE = 100;
+
+ public static final String ANY = "ANY";
+ public static final String IN = "IN";
+ public static final String OUT = "OUT";
+ public static final Set<String> AVAILABLE_MEMBERSHIP = ImmutableSet.of(ANY, IN, OUT);
+
+ private final String permission;
+ private final String component;
+ private final String template;
+ private final String membership;
+ private final String search;
+
+ // for internal use in MyBatis
+ final String searchSql;
+
+ // max results per page
+ private final int pageSize;
+
+ // index of selected page. Start with 1.
+ private final int pageIndex;
+
+ private PermissionQuery(Builder builder) {
+ this.permission = builder.permission;
+ this.component = builder.component;
+ this.template = builder.template;
+ this.membership = builder.membership;
+ this.search = builder.search;
+ this.searchSql = searchToSql(search);
+
+ this.pageSize = builder.pageSize;
+ this.pageIndex = builder.pageIndex;
+ }
+
+ private static String searchToSql(@Nullable String s) {
+ String sql = null;
+ if (s != null) {
+ sql = StringUtils.replace(StringUtils.upperCase(s), "%", "/%");
+ sql = StringUtils.replace(sql, "_", "/_");
+ sql = "%" + sql + "%";
+ }
+ return sql;
+ }
+
+ public String permission() {
+ return permission;
+ }
+
+ /**
+ * Used only for permission template
+ */
+ public String template() {
+ return template;
+ }
+
+ /**
+ * Used on project permission
+ */
+ @CheckForNull
+ public String component() {
+ return component;
+ }
+
+ @CheckForNull
+ public String membership() {
+ return membership;
+ }
+
+ @CheckForNull
+ public String search() {
+ return search;
+ }
+
+ public int pageSize() {
+ return pageSize;
+ }
+
+ public int pageIndex() {
+ return pageIndex;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String permission;
+ private String component;
+ private String template;
+ private String membership;
+ private String search;
+
+ private Integer pageIndex = DEFAULT_PAGE_INDEX;
+ private Integer pageSize = DEFAULT_PAGE_SIZE;
+
+ private Builder() {
+ }
+
+ public Builder permission(String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ public Builder template(String template) {
+ this.template = template;
+ return this;
+ }
+
+ public Builder component(@Nullable String component) {
+ this.component = component;
+ return this;
+ }
+
+ public Builder membership(@Nullable String membership) {
+ this.membership = membership;
+ return this;
+ }
+
+ public Builder search(@Nullable String s) {
+ this.search = StringUtils.defaultIfBlank(s, null);
+ return this;
+ }
+
+ public Builder pageSize(@Nullable Integer i) {
+ this.pageSize = i;
+ return this;
+ }
+
+ public Builder pageIndex(@Nullable Integer i) {
+ this.pageIndex = i;
+ return this;
+ }
+
+ private void initMembership() {
+ if (membership == null) {
+ membership = PermissionQuery.ANY;
+ } else {
+ Preconditions.checkArgument(AVAILABLE_MEMBERSHIP.contains(membership),
+ "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIP);
+ }
+ }
+
+ private void initPageSize() {
+ if (pageSize == null) {
+ pageSize = DEFAULT_PAGE_SIZE;
+ }
+ }
+
+ private void initPageIndex() {
+ if (pageIndex == null) {
+ pageIndex = DEFAULT_PAGE_INDEX;
+ }
+ Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
+ }
+
+ public PermissionQuery build() {
+ Preconditions.checkNotNull(permission, "Permission cant be null.");
+ initMembership();
+ initPageIndex();
+ initPageSize();
+ return new PermissionQuery(this);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java
new file mode 100644
index 00000000000..050f19c1cb4
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java
@@ -0,0 +1,275 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.text.Normalizer;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.time.DateFormatUtils;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Maps.newHashMap;
+
+public class PermissionTemplateDao implements Dao {
+
+ public static final String QUERY_PARAMETER = "query";
+ public static final String TEMPLATE_ID_PARAMETER = "templateId";
+ private final MyBatis myBatis;
+ private final System2 system;
+
+ public PermissionTemplateDao(MyBatis myBatis, System2 system) {
+ this.myBatis = myBatis;
+ this.system = system;
+ }
+
+ /**
+ * @return a paginated list of users.
+ */
+ public List<UserWithPermissionDto> selectUsers(PermissionQuery query, Long templateId, int offset, int limit) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ Map<String, Object> params = newHashMap();
+ params.put(QUERY_PARAMETER, query);
+ params.put(TEMPLATE_ID_PARAMETER, templateId);
+ return mapper(session).selectUsers(params, new RowBounds(offset, limit));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @VisibleForTesting
+ List<UserWithPermissionDto> selectUsers(PermissionQuery query, Long templateId) {
+ return selectUsers(query, templateId, 0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * 'Anyone' group is not returned when it has not the asked permission.
+ * Membership parameter from query is not taking into account in order to deal more easily with the 'Anyone' group.
+ * @return a non paginated list of groups.
+ */
+ public List<GroupWithPermissionDto> selectGroups(PermissionQuery query, Long templateId) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ Map<String, Object> params = newHashMap();
+ params.put(QUERY_PARAMETER, query);
+ params.put(TEMPLATE_ID_PARAMETER, templateId);
+ params.put("anyoneGroup", DefaultGroups.ANYONE);
+ return mapper(session).selectGroups(params);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public PermissionTemplateDto selectTemplateByKey(DbSession session, String templateKey) {
+ return mapper(session).selectByKey(templateKey);
+ }
+
+ @CheckForNull
+ public PermissionTemplateDto selectTemplateByKey(String templateKey) {
+ DbSession session = myBatis.openSession(false);
+ try {
+ return selectTemplateByKey(session, templateKey);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public PermissionTemplateDto selectPermissionTemplate(DbSession session, String templateKey) {
+ PermissionTemplateDto permissionTemplate = null;
+ PermissionTemplateMapper mapper = mapper(session);
+ permissionTemplate = mapper.selectByKey(templateKey);
+ PermissionTemplateDto templateUsersPermissions = mapper.selectTemplateUsersPermissions(templateKey);
+ if (templateUsersPermissions != null) {
+ permissionTemplate.setUsersPermissions(templateUsersPermissions.getUsersPermissions());
+ }
+ PermissionTemplateDto templateGroupsPermissions = mapper.selectTemplateGroupsPermissions(templateKey);
+ if (templateGroupsPermissions != null) {
+ permissionTemplate.setGroupsByPermission(templateGroupsPermissions.getGroupsPermissions());
+ }
+ return permissionTemplate;
+ }
+
+ @CheckForNull
+ public PermissionTemplateDto selectPermissionTemplate(String templateKey) {
+ DbSession session = myBatis.openSession(false);
+ try {
+ return selectPermissionTemplate(session, templateKey);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PermissionTemplateDto> selectAllPermissionTemplates(DbSession session) {
+ return session.selectList("selectAllPermissionTemplates");
+ }
+
+ public List<PermissionTemplateDto> selectAllPermissionTemplates() {
+ DbSession session = myBatis.openSession(false);
+ try {
+ return selectAllPermissionTemplates(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public PermissionTemplateDto createPermissionTemplate(String templateName, @Nullable String description, @Nullable String keyPattern) {
+ Date creationDate = now();
+ PermissionTemplateDto permissionTemplate = new PermissionTemplateDto()
+ .setName(templateName)
+ .setKee(generateTemplateKee(templateName, creationDate))
+ .setDescription(description)
+ .setKeyPattern(keyPattern)
+ .setCreatedAt(creationDate)
+ .setUpdatedAt(creationDate);
+ SqlSession session = myBatis.openSession(false);
+ try {
+ mapper(session).insert(permissionTemplate);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ return permissionTemplate;
+ }
+
+ public void deletePermissionTemplate(Long templateId) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ PermissionTemplateMapper mapper = mapper(session);
+ mapper.deleteUsersPermissions(templateId);
+ mapper.deleteGroupsPermissions(templateId);
+ mapper.delete(templateId);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void updatePermissionTemplate(Long templateId, String templateName, @Nullable String description, @Nullable String keyPattern) {
+ PermissionTemplateDto permissionTemplate = new PermissionTemplateDto()
+ .setId(templateId)
+ .setName(templateName)
+ .setDescription(description)
+ .setKeyPattern(keyPattern)
+ .setUpdatedAt(now());
+ SqlSession session = myBatis.openSession(false);
+ try {
+ mapper(session).update(permissionTemplate);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void addUserPermission(Long templateId, Long userId, String permission) {
+ PermissionTemplateUserDto permissionTemplateUser = new PermissionTemplateUserDto()
+ .setTemplateId(templateId)
+ .setUserId(userId)
+ .setPermission(permission)
+ .setCreatedAt(now())
+ .setUpdatedAt(now());
+ SqlSession session = myBatis.openSession(false);
+ try {
+ mapper(session).insertUserPermission(permissionTemplateUser);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void removeUserPermission(Long templateId, Long userId, String permission) {
+ PermissionTemplateUserDto permissionTemplateUser = new PermissionTemplateUserDto()
+ .setTemplateId(templateId)
+ .setPermission(permission)
+ .setUserId(userId);
+ SqlSession session = myBatis.openSession(false);
+ try {
+ mapper(session).deleteUserPermission(permissionTemplateUser);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void addGroupPermission(Long templateId, @Nullable Long groupId, String permission) {
+ PermissionTemplateGroupDto permissionTemplateGroup = new PermissionTemplateGroupDto()
+ .setTemplateId(templateId)
+ .setPermission(permission)
+ .setGroupId(groupId)
+ .setCreatedAt(now())
+ .setUpdatedAt(now());
+ SqlSession session = myBatis.openSession(false);
+ try {
+ mapper(session).insertGroupPermission(permissionTemplateGroup);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void removeGroupPermission(Long templateId, @Nullable Long groupId, String permission) {
+ PermissionTemplateGroupDto permissionTemplateGroup = new PermissionTemplateGroupDto()
+ .setTemplateId(templateId)
+ .setPermission(permission)
+ .setGroupId(groupId);
+ SqlSession session = myBatis.openSession(false);
+ try {
+ mapper(session).deleteGroupPermission(permissionTemplateGroup);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Remove a group from all templates (used when removing a group)
+ */
+ public void removeByGroup(Long groupId, SqlSession session) {
+ session.getMapper(PermissionTemplateMapper.class).deleteByGroupId(groupId);
+ }
+
+ private String generateTemplateKee(String name, Date timeStamp) {
+ if (PermissionTemplateDto.DEFAULT.getName().equals(name)) {
+ return PermissionTemplateDto.DEFAULT.getKee();
+ }
+ String normalizedName = Normalizer.normalize(name, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "").replace(" ", "_");
+ return normalizedName.toLowerCase() + "_" + DateFormatUtils.format(timeStamp, "yyyyMMdd_HHmmss");
+ }
+
+ private Date now() {
+ return new Date(system.now());
+ }
+
+ private PermissionTemplateMapper mapper(SqlSession session) {
+ return session.getMapper(PermissionTemplateMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDto.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDto.java
new file mode 100644
index 00000000000..68f7c1da41c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDto.java
@@ -0,0 +1,129 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.Date;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class PermissionTemplateDto {
+
+ public static final PermissionTemplateDto DEFAULT = new PermissionTemplateDto()
+ .setName("Default template")
+ .setKee("default_template")
+ .setDescription("This permission template will be used as default when no other permission configuration is available");
+
+ private Long id;
+ private String name;
+ private String kee;
+ private String description;
+ private String keyPattern;
+ private List<PermissionTemplateUserDto> usersPermissions;
+ private List<PermissionTemplateGroupDto> groupsPermissions;
+ private Date createdAt;
+ private Date updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public PermissionTemplateDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public PermissionTemplateDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getKee() {
+ return kee;
+ }
+
+ public PermissionTemplateDto setKee(String kee) {
+ this.kee = kee;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public PermissionTemplateDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ @CheckForNull
+ public String getKeyPattern() {
+ return keyPattern;
+ }
+
+ public PermissionTemplateDto setKeyPattern(@Nullable String regexp) {
+ this.keyPattern = regexp;
+ return this;
+ }
+
+ @CheckForNull
+ public List<PermissionTemplateUserDto> getUsersPermissions() {
+ return usersPermissions;
+ }
+
+ public PermissionTemplateDto setUsersPermissions(@Nullable List<PermissionTemplateUserDto> usersPermissions) {
+ this.usersPermissions = usersPermissions;
+ return this;
+ }
+
+ @CheckForNull
+ public List<PermissionTemplateGroupDto> getGroupsPermissions() {
+ return groupsPermissions;
+ }
+
+ public PermissionTemplateDto setGroupsByPermission(@Nullable List<PermissionTemplateGroupDto> groupsPermissions) {
+ this.groupsPermissions = groupsPermissions;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public PermissionTemplateDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public PermissionTemplateDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateGroupDto.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateGroupDto.java
new file mode 100644
index 00000000000..dc1e11dd2c5
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateGroupDto.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.Date;
+import javax.annotation.Nullable;
+
+public class PermissionTemplateGroupDto {
+
+ private Long id;
+ private Long templateId;
+ private Long groupId;
+ private String permission;
+ private String groupName;
+ private Date createdAt;
+ private Date updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public PermissionTemplateGroupDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getTemplateId() {
+ return templateId;
+ }
+
+ public PermissionTemplateGroupDto setTemplateId(Long templateId) {
+ this.templateId = templateId;
+ return this;
+ }
+
+ public Long getGroupId() {
+ return groupId;
+ }
+
+ public PermissionTemplateGroupDto setGroupId(@Nullable Long groupId) {
+ this.groupId = groupId;
+ return this;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+
+ public PermissionTemplateGroupDto setPermission(String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public PermissionTemplateGroupDto setGroupName(String groupName) {
+ this.groupName = groupName;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public PermissionTemplateGroupDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public PermissionTemplateGroupDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java
new file mode 100644
index 00000000000..1277462b14d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.session.RowBounds;
+
+/**
+ * @since 3.7
+ */
+public interface PermissionTemplateMapper {
+
+ void insert(PermissionTemplateDto permissionTemplate);
+
+ void update(PermissionTemplateDto permissionTemplate);
+
+ void delete(long templateId);
+
+ void deleteUsersPermissions(long templateId);
+
+ void deleteGroupsPermissions(long templateId);
+
+ PermissionTemplateDto selectByKey(String templateKey);
+
+ PermissionTemplateDto selectTemplateUsersPermissions(String templateKey);
+
+ PermissionTemplateDto selectTemplateGroupsPermissions(String templateKey);
+
+ void insertUserPermission(PermissionTemplateUserDto permissionTemplateUser);
+
+ void deleteUserPermission(PermissionTemplateUserDto permissionTemplateUser);
+
+ void insertGroupPermission(PermissionTemplateGroupDto permissionTemplateGroup);
+
+ void deleteGroupPermission(PermissionTemplateGroupDto permissionTemplateGroup);
+
+ void deleteByGroupId(long groupId);
+
+ List<GroupWithPermissionDto> selectGroups(Map<String, Object> params);
+
+ List<UserWithPermissionDto> selectUsers(Map<String, Object> params, RowBounds rowBounds);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateUserDto.java b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateUserDto.java
new file mode 100644
index 00000000000..a1f6aaacb2f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateUserDto.java
@@ -0,0 +1,107 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.Date;
+
+public class PermissionTemplateUserDto {
+
+ private Long id;
+ private Long templateId;
+ private Long userId;
+ private String permission;
+ private String userName;
+ private String userLogin;
+ private Date createdAt;
+ private Date updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public PermissionTemplateUserDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getTemplateId() {
+ return templateId;
+ }
+
+ public PermissionTemplateUserDto setTemplateId(Long templateId) {
+ this.templateId = templateId;
+ return this;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public PermissionTemplateUserDto setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public PermissionTemplateUserDto setUserName(String userName) {
+ this.userName = userName;
+ return this;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public PermissionTemplateUserDto setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ return this;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+
+ public PermissionTemplateUserDto setPermission(String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public PermissionTemplateUserDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public PermissionTemplateUserDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/UserWithPermissionDto.java b/sonar-db/src/main/java/org/sonar/db/permission/UserWithPermissionDto.java
new file mode 100644
index 00000000000..66c7d176c96
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/UserWithPermissionDto.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.core.permission.UserWithPermission;
+
+public class UserWithPermissionDto {
+
+ private String login;
+ private String name;
+ private String permission;
+
+ public String getLogin() {
+ return login;
+ }
+
+ public UserWithPermissionDto setLogin(String login) {
+ this.login = login;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public UserWithPermissionDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPermission() {
+ return permission;
+ }
+
+ public UserWithPermissionDto setPermission(@Nullable String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ public UserWithPermission toUserWithPermission() {
+ return new UserWithPermission()
+ .setLogin(login)
+ .setName(name)
+ .hasPermission(permission != null);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/permission/package-info.java b/sonar-db/src/main/java/org/sonar/db/permission/package-info.java
new file mode 100644
index 00000000000..bb23b9cf500
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/permission/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.permission;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/profiling/InvocationUtils.java b/sonar-db/src/main/java/org/sonar/db/profiling/InvocationUtils.java
new file mode 100644
index 00000000000..a4c5ac1cdc0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/profiling/InvocationUtils.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.profiling;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class InvocationUtils {
+
+ private InvocationUtils() {
+ // Only private stuff
+ }
+
+ static Object invokeQuietly(Object target, Method method, Object[] params) throws Throwable {
+ Object result = null;
+ try {
+ result = method.invoke(target, params);
+ } catch (InvocationTargetException invocationException) {
+ for (Class<?> exceptionClass : method.getExceptionTypes()) {
+ if (exceptionClass.isInstance(invocationException.getCause())) {
+ throw invocationException.getCause();
+ }
+ throw new IllegalStateException(invocationException.getCause());
+ }
+ }
+ return result;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java b/sonar-db/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java
new file mode 100644
index 00000000000..2b2136a1ef8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java
@@ -0,0 +1,407 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.profiling;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Proxy;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collection;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class ProfiledDataSource extends BasicDataSource {
+
+ static final Logger SQL_LOGGER = Loggers.get("sql");
+
+ private final BasicDataSource delegate;
+
+ public ProfiledDataSource(BasicDataSource delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean getDefaultAutoCommit() {
+ return delegate.getDefaultAutoCommit();
+ }
+
+ @Override
+ public void setDefaultAutoCommit(boolean defaultAutoCommit) {
+ delegate.setDefaultAutoCommit(defaultAutoCommit);
+ }
+
+ @Override
+ public boolean getDefaultReadOnly() {
+ return delegate.getDefaultReadOnly();
+ }
+
+ @Override
+ public void setDefaultReadOnly(boolean defaultReadOnly) {
+ delegate.setDefaultReadOnly(defaultReadOnly);
+ }
+
+ @Override
+ public int getDefaultTransactionIsolation() {
+ return delegate.getDefaultTransactionIsolation();
+ }
+
+ @Override
+ public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
+ delegate.setDefaultTransactionIsolation(defaultTransactionIsolation);
+ }
+
+ @Override
+ public String getDefaultCatalog() {
+ return delegate.getDefaultCatalog();
+ }
+
+ @Override
+ public void setDefaultCatalog(String defaultCatalog) {
+ delegate.setDefaultCatalog(defaultCatalog);
+ }
+
+ @Override
+ public synchronized String getDriverClassName() {
+ return delegate.getDriverClassName();
+ }
+
+ @Override
+ public synchronized void setDriverClassName(String driverClassName) {
+ delegate.setDriverClassName(driverClassName);
+ }
+
+ @Override
+ public synchronized ClassLoader getDriverClassLoader() {
+ return delegate.getDriverClassLoader();
+ }
+
+ @Override
+ public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) {
+ delegate.setDriverClassLoader(driverClassLoader);
+ }
+
+ @Override
+ public synchronized int getMaxActive() {
+ return delegate.getMaxActive();
+ }
+
+ @Override
+ public synchronized void setMaxActive(int maxActive) {
+ delegate.setMaxActive(maxActive);
+ }
+
+ @Override
+ public synchronized int getMaxIdle() {
+ return delegate.getMaxIdle();
+ }
+
+ @Override
+ public synchronized void setMaxIdle(int maxIdle) {
+ delegate.setMaxIdle(maxIdle);
+ }
+
+ @Override
+ public synchronized int getMinIdle() {
+ return delegate.getMinIdle();
+ }
+
+ @Override
+ public synchronized void setMinIdle(int minIdle) {
+ delegate.setMinIdle(minIdle);
+ }
+
+ @Override
+ public synchronized int getInitialSize() {
+ return delegate.getInitialSize();
+ }
+
+ @Override
+ public synchronized void setInitialSize(int initialSize) {
+ delegate.setInitialSize(initialSize);
+ }
+
+ @Override
+ public synchronized long getMaxWait() {
+ return delegate.getMaxWait();
+ }
+
+ @Override
+ public synchronized void setMaxWait(long maxWait) {
+ delegate.setMaxWait(maxWait);
+ }
+
+ @Override
+ public synchronized boolean isPoolPreparedStatements() {
+ return delegate.isPoolPreparedStatements();
+ }
+
+ @Override
+ public synchronized void setPoolPreparedStatements(boolean poolingStatements) {
+ delegate.setPoolPreparedStatements(poolingStatements);
+ }
+
+ @Override
+ public synchronized int getMaxOpenPreparedStatements() {
+ return delegate.getMaxOpenPreparedStatements();
+ }
+
+ @Override
+ public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) {
+ delegate.setMaxOpenPreparedStatements(maxOpenStatements);
+ }
+
+ @Override
+ public synchronized boolean getTestOnBorrow() {
+ return delegate.getTestOnBorrow();
+ }
+
+ @Override
+ public synchronized void setTestOnBorrow(boolean testOnBorrow) {
+ delegate.setTestOnBorrow(testOnBorrow);
+ }
+
+ @Override
+ public synchronized boolean getTestOnReturn() {
+ return delegate.getTestOnReturn();
+ }
+
+ @Override
+ public synchronized void setTestOnReturn(boolean testOnReturn) {
+ delegate.setTestOnReturn(testOnReturn);
+ }
+
+ @Override
+ public synchronized long getTimeBetweenEvictionRunsMillis() {
+ return delegate.getTimeBetweenEvictionRunsMillis();
+ }
+
+ @Override
+ public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
+ delegate.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+ }
+
+ @Override
+ public synchronized int getNumTestsPerEvictionRun() {
+ return delegate.getNumTestsPerEvictionRun();
+ }
+
+ @Override
+ public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
+ delegate.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
+ }
+
+ @Override
+ public synchronized long getMinEvictableIdleTimeMillis() {
+ return delegate.getMinEvictableIdleTimeMillis();
+ }
+
+ @Override
+ public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
+ delegate.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+ }
+
+ @Override
+ public synchronized boolean getTestWhileIdle() {
+ return delegate.getTestWhileIdle();
+ }
+
+ @Override
+ public synchronized void setTestWhileIdle(boolean testWhileIdle) {
+ delegate.setTestWhileIdle(testWhileIdle);
+ }
+
+ @Override
+ public synchronized int getNumActive() {
+ return delegate.getNumActive();
+ }
+
+ @Override
+ public synchronized int getNumIdle() {
+ return delegate.getNumIdle();
+ }
+
+ @Override
+ public String getPassword() {
+ return delegate.getPassword();
+ }
+
+ @Override
+ public void setPassword(String password) {
+ delegate.setPassword(password);
+ }
+
+ @Override
+ public synchronized String getUrl() {
+ return delegate.getUrl();
+ }
+
+ @Override
+ public synchronized void setUrl(String url) {
+ delegate.setUrl(url);
+ }
+
+ @Override
+ public String getUsername() {
+ return delegate.getUsername();
+ }
+
+ @Override
+ public void setUsername(String username) {
+ delegate.setUsername(username);
+ }
+
+ @Override
+ public String getValidationQuery() {
+ return delegate.getValidationQuery();
+ }
+
+ @Override
+ public void setValidationQuery(String validationQuery) {
+ delegate.setValidationQuery(validationQuery);
+ }
+
+ @Override
+ public int getValidationQueryTimeout() {
+ return delegate.getValidationQueryTimeout();
+ }
+
+ @Override
+ public void setValidationQueryTimeout(int timeout) {
+ delegate.setValidationQueryTimeout(timeout);
+ }
+
+ @Override
+ public Collection getConnectionInitSqls() {
+ return delegate.getConnectionInitSqls();
+ }
+
+ @Override
+ public void setConnectionInitSqls(Collection connectionInitSqls) {
+ delegate.setConnectionInitSqls(connectionInitSqls);
+ }
+
+ @Override
+ public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
+ return delegate.isAccessToUnderlyingConnectionAllowed();
+ }
+
+ @Override
+ public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
+ delegate.setAccessToUnderlyingConnectionAllowed(allow);
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {Connection.class},
+ new ProfilingConnectionHandler(delegate.getConnection()));
+ }
+
+ @Override
+ public Connection getConnection(String user, String pass) throws SQLException {
+ return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {Connection.class},
+ new ProfilingConnectionHandler(delegate.getConnection(user, pass)));
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return delegate.getLoginTimeout();
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return delegate.getLogWriter();
+ }
+
+ @Override
+ public void setLoginTimeout(int loginTimeout) throws SQLException {
+ delegate.setLoginTimeout(loginTimeout);
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter logWriter) throws SQLException {
+ delegate.setLogWriter(logWriter);
+ }
+
+ @Override
+ public boolean getRemoveAbandoned() {
+ return delegate.getRemoveAbandoned();
+ }
+
+ @Override
+ public void setRemoveAbandoned(boolean removeAbandoned) {
+ delegate.setRemoveAbandoned(removeAbandoned);
+ }
+
+ @Override
+ public int getRemoveAbandonedTimeout() {
+ return delegate.getRemoveAbandonedTimeout();
+ }
+
+ @Override
+ public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
+ delegate.setRemoveAbandonedTimeout(removeAbandonedTimeout);
+ }
+
+ @Override
+ public boolean getLogAbandoned() {
+ return delegate.getLogAbandoned();
+ }
+
+ @Override
+ public void setLogAbandoned(boolean logAbandoned) {
+ delegate.setLogAbandoned(logAbandoned);
+ }
+
+ @Override
+ public void addConnectionProperty(String name, String value) {
+ delegate.addConnectionProperty(name, value);
+ }
+
+ @Override
+ public void removeConnectionProperty(String name) {
+ delegate.removeConnectionProperty(name);
+ }
+
+ @Override
+ public void setConnectionProperties(String connectionProperties) {
+ delegate.setConnectionProperties(connectionProperties);
+ }
+
+ @Override
+ public synchronized void close() throws SQLException {
+ delegate.close();
+ }
+
+ @Override
+ public synchronized boolean isClosed() {
+ return delegate.isClosed();
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return delegate.isWrapperFor(iface);
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ return delegate.unwrap(iface);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java b/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java
new file mode 100644
index 00000000000..4446a47e7ee
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.profiling;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+
+class ProfilingConnectionHandler implements InvocationHandler {
+
+ private final Connection connection;
+
+ ProfilingConnectionHandler(Connection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public Object invoke(Object target, Method method, Object[] args) throws Throwable {
+ Object result = InvocationUtils.invokeQuietly(connection, method, args);
+ if ("prepareStatement".equals(method.getName())) {
+ PreparedStatement statement = (PreparedStatement) result;
+ String sql = (String) args[0];
+ return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] {PreparedStatement.class},
+ new ProfilingPreparedStatementHandler(statement, sql));
+
+ } else if ("createStatement".equals(method.getName())) {
+ Statement statement = (Statement) result;
+ return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] {Statement.class},
+ new ProfilingStatementHandler(statement));
+
+ } else {
+ return result;
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java b/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java
new file mode 100644
index 00000000000..9c451f13a30
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.profiling;
+
+import com.google.common.collect.Lists;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.PreparedStatement;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.log.Profiler;
+
+class ProfilingPreparedStatementHandler implements InvocationHandler {
+
+ private final PreparedStatement statement;
+ private final List<Object> arguments;
+ private final String sql;
+
+ ProfilingPreparedStatementHandler(PreparedStatement statement, String sql) {
+ this.statement = statement;
+ this.sql = sql;
+ this.arguments = Lists.newArrayList();
+ for (int argCount = 0; argCount < StringUtils.countMatches(sql, "?"); argCount++) {
+ arguments.add("!");
+ }
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().startsWith("execute")) {
+ Profiler profiler = Profiler.create(ProfiledDataSource.SQL_LOGGER).start();
+ Object result = null;
+ try {
+ result = InvocationUtils.invokeQuietly(statement, method, args);
+ } finally {
+ profiler.addContext("sql", StringUtils.remove(sql, '\n'));
+ profiler.stopTrace("");
+ }
+ return result;
+ } else if (method.getName().startsWith("set") && args.length > 1) {
+ arguments.set((Integer) args[0] - 1, args[1]);
+ return InvocationUtils.invokeQuietly(statement, method, args);
+ } else {
+ return InvocationUtils.invokeQuietly(statement, method, args);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java b/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java
new file mode 100644
index 00000000000..994f1a823c0
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.profiling;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.Statement;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.log.Profiler;
+
+class ProfilingStatementHandler implements InvocationHandler {
+
+ private final Statement statement;
+
+ ProfilingStatementHandler(Statement statement) {
+ this.statement = statement;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().startsWith("execute")) {
+ Profiler profiler = Profiler.create(ProfiledDataSource.SQL_LOGGER).start();
+ Object result = null;
+ try {
+ result = InvocationUtils.invokeQuietly(statement, method, args);
+ } finally {
+ profiler.addContext("sql", StringUtils.remove((String) args[0], '\n'));
+ profiler.stopTrace("");
+ }
+ return result;
+ } else {
+ return InvocationUtils.invokeQuietly(statement, method, args);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/profiling/package-info.java b/sonar-db/src/main/java/org/sonar/db/profiling/package-info.java
new file mode 100644
index 00000000000..99f88d56e77
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/profiling/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.profiling;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java b/sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java
new file mode 100644
index 00000000000..cb1a63adbe3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java
@@ -0,0 +1,302 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.property;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.resources.Scopes;
+import org.sonar.db.Dao;
+import org.sonar.db.DaoUtils;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class PropertiesDao implements Dao {
+
+ private static final String NOTIFICATION_PREFIX = "notification.";
+ private MyBatis mybatis;
+
+ public PropertiesDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ /**
+ * Returns the logins of users who have subscribed to the given notification dispatcher with the given notification channel.
+ * If a resource ID is passed, the search is made on users who have specifically subscribed for the given resource.
+ *
+ * @return the list of logins (maybe be empty - obviously)
+ */
+ public List<String> findUsersForNotification(String notificationDispatcherKey, String notificationChannelKey,
+ @Nullable String projectUuid) {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ return mapper.findUsersForNotification(NOTIFICATION_PREFIX + notificationDispatcherKey + "." + notificationChannelKey, projectUuid);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<String> findNotificationSubscribers(String notificationDispatcherKey, String notificationChannelKey, @Nullable String componentKey) {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ return mapper.findNotificationSubscribers(NOTIFICATION_PREFIX + notificationDispatcherKey + "." + notificationChannelKey, componentKey);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public boolean hasProjectNotificationSubscribersForDispatchers(String projectUuid, Collection<String> dispatcherKeys) {
+ DbSession session = mybatis.openSession(false);
+ Connection connection = session.getConnection();
+ PreparedStatement pstmt = null;
+ ResultSet rs = null;
+ String sql = "SELECT count(*) FROM properties pp " +
+ "left outer join projects pj on pp.resource_id = pj.id " +
+ "where pp.user_id is not null and (pp.resource_id is null or pj.uuid=?) " +
+ "and (" + DaoUtils.repeatCondition("pp.prop_key like ?", dispatcherKeys.size(), "or") + ")";
+ try {
+ pstmt = connection.prepareStatement(sql);
+ pstmt.setString(1, projectUuid);
+ int index = 2;
+ for (String dispatcherKey : dispatcherKeys) {
+ pstmt.setString(index, "notification." + dispatcherKey + ".%");
+ index++;
+ }
+ rs = pstmt.executeQuery();
+ return rs.next() && rs.getInt(1) > 0;
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to execute SQL request: " + sql, e);
+ } finally {
+ DbUtils.closeQuietly(connection, pstmt, rs);
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PropertyDto> selectGlobalProperties() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectGlobalProperties(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PropertyDto> selectGlobalProperties(SqlSession session) {
+ return session.getMapper(PropertiesMapper.class).selectGlobalProperties();
+ }
+
+ public PropertyDto selectGlobalProperty(String propertyKey) {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ return mapper.selectByKey(new PropertyDto().setKey(propertyKey));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PropertyDto> selectProjectProperties(DbSession session, String projectKey) {
+ return session.getMapper(PropertiesMapper.class).selectProjectProperties(projectKey);
+ }
+
+ public List<PropertyDto> selectProjectProperties(String resourceKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return selectProjectProperties(session, resourceKey);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PropertyDto> selectEnabledDescendantModuleProperties(String moduleUuid, SqlSession session) {
+ return session.getMapper(PropertiesMapper.class).selectDescendantModuleProperties(moduleUuid, Scopes.PROJECT, true);
+ }
+
+ public PropertyDto selectProjectProperty(long resourceId, String propertyKey) {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ return mapper.selectByKey(new PropertyDto().setKey(propertyKey).setResourceId(resourceId));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PropertyDto> selectByQuery(PropertyQuery query, DbSession session) {
+ return session.getMapper(PropertiesMapper.class).selectByQuery(query);
+ }
+
+ public void setProperty(PropertyDto property, SqlSession session) {
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ PropertyDto persistedProperty = mapper.selectByKey(property);
+ if (persistedProperty != null && !StringUtils.equals(persistedProperty.getValue(), property.getValue())) {
+ persistedProperty.setValue(property.getValue());
+ mapper.update(persistedProperty);
+ } else if (persistedProperty == null) {
+ mapper.insert(property);
+ }
+ }
+
+ public void setProperty(PropertyDto property) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ setProperty(property, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteProjectProperty(String key, Long projectId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ deleteProjectProperty(key, projectId, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteProjectProperty(String key, Long projectId, SqlSession session) {
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ mapper.deleteProjectProperty(key, projectId);
+ }
+
+ public void deleteProjectProperties(String key, String value, SqlSession session) {
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ mapper.deleteProjectProperties(key, value);
+ }
+
+ public void deleteProjectProperties(String key, String value) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ deleteProjectProperties(key, value, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteGlobalProperties() {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ mapper.deleteGlobalProperties();
+ session.commit();
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteGlobalProperty(String key, SqlSession session) {
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ mapper.deleteGlobalProperty(key);
+ }
+
+ public void deleteGlobalProperty(String key) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ deleteGlobalProperty(key, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteAllProperties(String key) {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ mapper.deleteAllProperties(key);
+ session.commit();
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void saveGlobalProperties(Map<String, String> properties) {
+ DbSession session = mybatis.openSession(true);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ mapper.deleteGlobalProperty(entry.getKey());
+ }
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ mapper.insert(new PropertyDto().setKey(entry.getKey()).setValue(entry.getValue()));
+ }
+ session.commit();
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void renamePropertyKey(String oldKey, String newKey) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(oldKey), "Old property key must not be empty");
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(newKey), "New property key must not be empty");
+
+ if (!newKey.equals(oldKey)) {
+ SqlSession session = mybatis.openSession(false);
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ mapper.renamePropertyKey(oldKey, newKey);
+ session.commit();
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+ }
+
+ /**
+ * Update all properties (global and projects ones) with a given key and value to a new value
+ */
+ public void updateProperties(String key, String oldValue, String newValue) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ updateProperties(key, oldValue, newValue, session);
+ session.commit();
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void updateProperties(String key, String oldValue, String newValue, SqlSession session) {
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ mapper.updateProperties(key, oldValue, newValue);
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java b/sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java
new file mode 100644
index 00000000000..34c03e2e9b8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.property;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+public interface PropertiesMapper {
+
+ List<String> findUsersForNotification(@Param("notifKey") String notificationKey, @Nullable @Param("projectUuid") String projectUuid);
+
+ List<String> findNotificationSubscribers(@Param("propKey") String propertyKey, @Nullable @Param("componentKey") String componentKey);
+
+ List<PropertyDto> selectGlobalProperties();
+
+ List<PropertyDto> selectProjectProperties(String resourceKey);
+
+ List<PropertyDto> selectProjectPropertiesByResourceId(Long resourceId);
+
+ List<PropertyDto> selectSetOfResourceProperties(@Param("rId") Long projectId, @Param("propKeys") List<String> propertyKeys);
+
+ PropertyDto selectByKey(PropertyDto key);
+
+ List<PropertyDto> selectByQuery(@Param("query") PropertyQuery query);
+
+ List<PropertyDto> selectDescendantModuleProperties(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,
+ @Param(value = "excludeDisabled") boolean excludeDisabled);
+
+ void update(PropertyDto property);
+
+ void insert(PropertyDto property);
+
+ void deleteProjectProperty(@Param("key") String key, @Param("rId") Long resourceId);
+
+ void deleteProjectProperties(@Param("key") String key, @Param("value") String value);
+
+ void deleteGlobalProperty(String key);
+
+ void deleteAllProperties(String key);
+
+ void deleteGlobalProperties();
+
+ void renamePropertyKey(@Param("oldKey") String oldKey, @Param("newKey") String newKey);
+
+ void updateProperties(@Param("key") String key, @Param("oldValue") String oldValue, @Param("newValue") String newValue);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/property/PropertyDto.java b/sonar-db/src/main/java/org/sonar/db/property/PropertyDto.java
new file mode 100644
index 00000000000..10456ace24c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/property/PropertyDto.java
@@ -0,0 +1,106 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.property;
+
+import com.google.common.base.Objects;
+
+public final class PropertyDto {
+ private Long id;
+ private String key;
+ private String value;
+ private Long resourceId;
+ private Long userId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public PropertyDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public PropertyDto setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public PropertyDto setValue(String value) {
+ this.value = value;
+ return this;
+ }
+
+ public Long getResourceId() {
+ return resourceId;
+ }
+
+ public PropertyDto setResourceId(Long resourceId) {
+ this.resourceId = resourceId;
+ return this;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public PropertyDto setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PropertyDto other = (PropertyDto) obj;
+
+ return Objects.equal(this.key, other.key)
+ && Objects.equal(this.value, other.value)
+ && Objects.equal(this.userId, other.userId)
+ && Objects.equal(this.resourceId, other.resourceId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.key, this.value, this.resourceId, this.userId);
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .addValue(this.key)
+ .addValue(this.value)
+ .addValue(this.resourceId)
+ .addValue(this.userId)
+ .toString();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/property/PropertyQuery.java b/sonar-db/src/main/java/org/sonar/db/property/PropertyQuery.java
new file mode 100644
index 00000000000..108fb2e2742
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/property/PropertyQuery.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.property;
+
+public class PropertyQuery {
+
+ private final String key;
+ private final Long componentId;
+ private final Integer userId;
+
+ private PropertyQuery(Builder builder) {
+ this.key = builder.key;
+ this.componentId = builder.componentId;
+ this.userId = builder.userId;
+ }
+
+ public String key() {
+ return key;
+ }
+
+ public Long componentId() {
+ return componentId;
+ }
+
+ public Integer userId() {
+ return userId;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String key;
+ private Long componentId;
+ private Integer userId;
+
+ public Builder setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder setComponentId(Long componentId) {
+ this.componentId = componentId;
+ return this;
+ }
+
+ public Builder setUserId(Integer userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public PropertyQuery build() {
+ return new PropertyQuery(this);
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPair.java b/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPair.java
new file mode 100644
index 00000000000..642a6fbda55
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPair.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge;
+
+public class IdUuidPair {
+ private Long id;
+ private String uuid;
+
+ public IdUuidPair() {
+ }
+
+ public IdUuidPair(long id, String uuid) {
+ this.id = id;
+ this.uuid = uuid;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java b/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java
new file mode 100644
index 00000000000..12bfbab8f09
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/IdUuidPairs.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import java.util.List;
+import javax.annotation.Nonnull;
+
+public class IdUuidPairs {
+ private IdUuidPairs() {
+ }
+
+ public static List<Long> ids(List<IdUuidPair> pairs) {
+ return Lists.transform(pairs, new IdUuidPairToIdFunction());
+ }
+
+ public static List<String> uuids(List<IdUuidPair> pairs) {
+ return Lists.transform(pairs, new IdUuidPairToUuidFunction());
+ }
+
+ private static class IdUuidPairToIdFunction implements Function<IdUuidPair, Long> {
+ @Override
+ public Long apply(@Nonnull IdUuidPair pair) {
+ return pair.getId();
+ }
+ }
+
+ private static class IdUuidPairToUuidFunction implements Function<IdUuidPair, String> {
+ @Override
+ public String apply(@Nonnull IdUuidPair pair) {
+ return pair.getUuid();
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java
new file mode 100644
index 00000000000..8e93918e780
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeCommands.java
@@ -0,0 +1,224 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+
+class PurgeCommands {
+
+ private static final int MAX_SNAPSHOTS_PER_QUERY = 1000;
+ private static final int MAX_RESOURCES_PER_QUERY = 1000;
+
+ private final SqlSession session;
+ private final PurgeMapper purgeMapper;
+ private final PurgeProfiler profiler;
+
+ PurgeCommands(SqlSession session, PurgeMapper purgeMapper, PurgeProfiler profiler) {
+ this.session = session;
+ this.purgeMapper = purgeMapper;
+ this.profiler = profiler;
+ }
+
+ @VisibleForTesting
+ PurgeCommands(SqlSession session, PurgeProfiler profiler) {
+ this(session, session.getMapper(PurgeMapper.class), profiler);
+ }
+
+ List<Long> selectSnapshotIds(PurgeSnapshotQuery query) {
+ return purgeMapper.selectSnapshotIds(query);
+ }
+
+ void deleteResources(List<IdUuidPair> componentIdUuids) {
+ List<List<Long>> componentIdPartitions = Lists.partition(IdUuidPairs.ids(componentIdUuids), MAX_RESOURCES_PER_QUERY);
+ List<List<String>> componentUuidsPartitions = Lists.partition(IdUuidPairs.uuids(componentIdUuids), MAX_RESOURCES_PER_QUERY);
+ // Note : do not merge the delete statements into a single loop of resource ids. It's
+ // voluntarily grouped by tables in order to benefit from JDBC batch mode.
+ // Batch requests can only relate to the same PreparedStatement.
+
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ deleteSnapshots(purgeMapper.selectSnapshotIdsByResource(partResourceIds));
+ }
+
+ // possible missing optimization: filter requests according to resource scope
+
+ profiler.start("deleteResourceLinks (project_links)");
+ for (List<String> componentUuidPartition : componentUuidsPartitions) {
+ purgeMapper.deleteResourceLinks(componentUuidPartition);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResourceProperties (properties)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteResourceProperties(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResourceIndex (resource_index)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteResourceIndex(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResourceGroupRoles (group_roles)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteResourceGroupRoles(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResourceUserRoles (user_roles)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteResourceUserRoles(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResourceManualMeasures (manual_measures)");
+ for (List<String> componentUuidPartition : componentUuidsPartitions) {
+ purgeMapper.deleteResourceManualMeasures(componentUuidPartition);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteComponentIssueChanges (issue_changes)");
+ for (List<String> componentUuidPartition : componentUuidsPartitions) {
+ purgeMapper.deleteComponentIssueChanges(componentUuidPartition);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteComponentIssues (issues)");
+ for (List<String> componentUuidPartition : componentUuidsPartitions) {
+ purgeMapper.deleteComponentIssues(componentUuidPartition);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResourceActionPlans (action_plans)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteResourceActionPlans(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteComponentEvents (events)");
+ for (List<String> componentUuidPartition : componentUuidsPartitions) {
+ purgeMapper.deleteComponentEvents(componentUuidPartition);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteResource (projects)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteResource(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteAuthors (authors)");
+ for (List<Long> partResourceIds : componentIdPartitions) {
+ purgeMapper.deleteAuthors(partResourceIds);
+ }
+ session.commit();
+ profiler.stop();
+ }
+
+ void deleteSnapshots(final PurgeSnapshotQuery query) {
+ deleteSnapshots(purgeMapper.selectSnapshotIds(query));
+ }
+
+ @VisibleForTesting
+ protected void deleteSnapshots(final List<Long> snapshotIds) {
+
+ List<List<Long>> snapshotIdsPartition = Lists.partition(snapshotIds, MAX_SNAPSHOTS_PER_QUERY);
+
+ deleteSnapshotDuplications(snapshotIdsPartition);
+
+ profiler.start("deleteSnapshotEvents (events)");
+ for (List<Long> partSnapshotIds : snapshotIdsPartition) {
+ purgeMapper.deleteSnapshotEvents(partSnapshotIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteSnapshotMeasures (project_measures)");
+ for (List<Long> partSnapshotIds : snapshotIdsPartition) {
+ purgeMapper.deleteSnapshotMeasures(partSnapshotIds);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("deleteSnapshot (snapshots)");
+ for (List<Long> partSnapshotIds : snapshotIdsPartition) {
+ purgeMapper.deleteSnapshot(partSnapshotIds);
+ }
+ session.commit();
+ profiler.stop();
+ }
+
+ void purgeSnapshots(final PurgeSnapshotQuery query) {
+ purgeSnapshots(purgeMapper.selectSnapshotIds(query));
+ }
+
+ @VisibleForTesting
+ protected void purgeSnapshots(final List<Long> snapshotIds) {
+ // note that events are not deleted
+ List<List<Long>> snapshotIdsPartition = Lists.partition(snapshotIds, MAX_SNAPSHOTS_PER_QUERY);
+
+ deleteSnapshotDuplications(snapshotIdsPartition);
+
+ profiler.start("deleteSnapshotWastedMeasures (project_measures)");
+ List<Long> metricIdsWithoutHistoricalData = purgeMapper.selectMetricIdsWithoutHistoricalData();
+ for (List<Long> partSnapshotIds : snapshotIdsPartition) {
+ purgeMapper.deleteSnapshotWastedMeasures(partSnapshotIds, metricIdsWithoutHistoricalData);
+ }
+ session.commit();
+ profiler.stop();
+
+ profiler.start("updatePurgeStatusToOne (snapshots)");
+ for (Long snapshotId : snapshotIds) {
+ purgeMapper.updatePurgeStatusToOne(snapshotId);
+ }
+ session.commit();
+ profiler.stop();
+ }
+
+ private void deleteSnapshotDuplications(final List<List<Long>> snapshotIdsPartition) {
+ profiler.start("deleteSnapshotDuplications (duplications_index)");
+ for (List<Long> partSnapshotIds : snapshotIdsPartition) {
+ purgeMapper.deleteSnapshotDuplications(partSnapshotIds);
+ }
+ session.commit();
+ profiler.stop();
+ }
+
+ public void deleteFileSources(String rootUuid) {
+ profiler.start("deleteFileSources (file_sources)");
+ purgeMapper.deleteFileSourcesByProjectUuid(rootUuid);
+ session.commit();
+ profiler.stop();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeConfiguration.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeConfiguration.java
new file mode 100644
index 00000000000..65bc6c0ca0e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeConfiguration.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import org.apache.commons.lang.time.DateUtils;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
+import org.sonar.core.config.PurgeConstants;
+
+public class PurgeConfiguration {
+
+ private final IdUuidPair rootProjectIdUuid;
+ private final String[] scopesWithoutHistoricalData;
+ private final int maxAgeInDaysOfClosedIssues;
+ private final System2 system2;
+
+ public PurgeConfiguration(IdUuidPair rootProjectId, String[] scopesWithoutHistoricalData, int maxAgeInDaysOfClosedIssues) {
+ this(rootProjectId, scopesWithoutHistoricalData, maxAgeInDaysOfClosedIssues, System2.INSTANCE);
+ }
+
+ @VisibleForTesting
+ PurgeConfiguration(IdUuidPair rootProjectId, String[] scopesWithoutHistoricalData, int maxAgeInDaysOfClosedIssues, System2 system2) {
+ this.rootProjectIdUuid = rootProjectId;
+ this.scopesWithoutHistoricalData = scopesWithoutHistoricalData;
+ this.maxAgeInDaysOfClosedIssues = maxAgeInDaysOfClosedIssues;
+ this.system2 = system2;
+ }
+
+ public static PurgeConfiguration newDefaultPurgeConfiguration(Settings settings, IdUuidPair idUuidPair) {
+ String[] scopes = new String[] {Scopes.FILE};
+ if (settings.getBoolean(PurgeConstants.PROPERTY_CLEAN_DIRECTORY)) {
+ scopes = new String[] {Scopes.DIRECTORY, Scopes.FILE};
+ }
+ return new PurgeConfiguration(idUuidPair, scopes, settings.getInt(PurgeConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES));
+ }
+
+ public IdUuidPair rootProjectIdUuid() {
+ return rootProjectIdUuid;
+ }
+
+ public String[] scopesWithoutHistoricalData() {
+ return scopesWithoutHistoricalData;
+ }
+
+ @CheckForNull
+ public Date maxLiveDateOfClosedIssues() {
+ return maxLiveDateOfClosedIssues(new Date(system2.now()));
+ }
+
+ @VisibleForTesting
+ @CheckForNull
+ Date maxLiveDateOfClosedIssues(Date now) {
+ if (maxAgeInDaysOfClosedIssues > 0) {
+ return DateUtils.addDays(now, -maxAgeInDaysOfClosedIssues);
+ }
+
+ // delete all closed issues
+ return null;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeDao.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeDao.java
new file mode 100644
index 00000000000..a06767ab51c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeDao.java
@@ -0,0 +1,244 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.SqlSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceDto;
+
+import static org.sonar.api.utils.DateUtils.dateToLong;
+
+/**
+ * @since 2.14
+ */
+public class PurgeDao implements Dao {
+ private static final Logger LOG = LoggerFactory.getLogger(PurgeDao.class);
+ private final MyBatis mybatis;
+ private final ResourceDao resourceDao;
+ private final System2 system2;
+ private final PurgeProfiler profiler;
+
+ public PurgeDao(MyBatis mybatis, ResourceDao resourceDao, PurgeProfiler profiler, System2 system2) {
+ this.mybatis = mybatis;
+ this.resourceDao = resourceDao;
+ this.profiler = profiler;
+ this.system2 = system2;
+ }
+
+ public PurgeDao purge(PurgeConfiguration conf, PurgeListener purgeListener) {
+ DbSession session = mybatis.openSession(true);
+ try {
+ purge(session, conf, purgeListener);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ return this;
+ }
+
+ public void purge(DbSession session, PurgeConfiguration conf, PurgeListener purgeListener) {
+ PurgeMapper mapper = session.getMapper(PurgeMapper.class);
+ PurgeCommands commands = new PurgeCommands(session, mapper, profiler);
+ List<ResourceDto> projects = getProjects(conf.rootProjectIdUuid().getId(), session);
+ for (ResourceDto project : projects) {
+ LOG.debug("-> Clean " + project.getLongName() + " [id=" + project.getId() + "]");
+ deleteAbortedBuilds(project, commands);
+ purge(project, conf.scopesWithoutHistoricalData(), commands);
+ }
+ for (ResourceDto project : projects) {
+ disableOrphanResources(project, session, mapper, purgeListener);
+ }
+ deleteOldClosedIssues(conf, mapper);
+ }
+
+ private void deleteOldClosedIssues(PurgeConfiguration conf, PurgeMapper mapper) {
+ Date toDate = conf.maxLiveDateOfClosedIssues();
+ mapper.deleteOldClosedIssueChanges(conf.rootProjectIdUuid().getUuid(), dateToLong(toDate));
+ mapper.deleteOldClosedIssues(conf.rootProjectIdUuid().getUuid(), dateToLong(toDate));
+ }
+
+ private void deleteAbortedBuilds(ResourceDto project, PurgeCommands commands) {
+ if (hasAbortedBuilds(project.getId(), commands)) {
+ LOG.debug("<- Delete aborted builds");
+ PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
+ .setIslast(false)
+ .setStatus(new String[] {"U"})
+ .setRootProjectId(project.getId());
+ commands.deleteSnapshots(query);
+ }
+ }
+
+ private boolean hasAbortedBuilds(Long projectId, PurgeCommands commands) {
+ PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
+ .setIslast(false)
+ .setStatus(new String[] {"U"})
+ .setResourceId(projectId);
+ return !commands.selectSnapshotIds(query).isEmpty();
+ }
+
+ private void purge(ResourceDto project, String[] scopesWithoutHistoricalData, PurgeCommands purgeCommands) {
+ List<Long> projectSnapshotIds = purgeCommands.selectSnapshotIds(
+ PurgeSnapshotQuery.create()
+ .setResourceId(project.getId())
+ .setIslast(false)
+ .setNotPurged(true)
+ );
+ for (final Long projectSnapshotId : projectSnapshotIds) {
+ LOG.debug("<- Clean snapshot " + projectSnapshotId);
+ if (!ArrayUtils.isEmpty(scopesWithoutHistoricalData)) {
+ PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
+ .setIslast(false)
+ .setScopes(scopesWithoutHistoricalData)
+ .setRootSnapshotId(projectSnapshotId);
+ purgeCommands.deleteSnapshots(query);
+ }
+
+ PurgeSnapshotQuery query = PurgeSnapshotQuery.create().setRootSnapshotId(projectSnapshotId).setNotPurged(true);
+ purgeCommands.purgeSnapshots(query);
+
+ // must be executed at the end for reentrance
+ purgeCommands.purgeSnapshots(PurgeSnapshotQuery.create().setId(projectSnapshotId).setNotPurged(true));
+ }
+ }
+
+ private void disableOrphanResources(final ResourceDto project, final SqlSession session, final PurgeMapper purgeMapper, final PurgeListener purgeListener) {
+ final List<IdUuidPair> componentIdUuids = new ArrayList<>();
+ session.select("org.sonar.db.purge.PurgeMapper.selectComponentIdUuidsToDisable", project.getId(), new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext resultContext) {
+ IdUuidPair componentIdUuid = (IdUuidPair) resultContext.getResultObject();
+ if (componentIdUuid.getId() != null) {
+ componentIdUuids.add(componentIdUuid);
+ }
+ }
+ });
+
+ for (IdUuidPair componentIdUuid : componentIdUuids) {
+ disableResource(componentIdUuid, purgeMapper);
+ purgeListener.onComponentDisabling(componentIdUuid.getUuid());
+ }
+
+ session.commit();
+ }
+
+ public List<PurgeableSnapshotDto> selectPurgeableSnapshots(long resourceId) {
+ DbSession session = mybatis.openSession(true);
+ try {
+ return selectPurgeableSnapshots(resourceId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<PurgeableSnapshotDto> selectPurgeableSnapshots(long resourceId, DbSession session) {
+ List<PurgeableSnapshotDto> result = Lists.newArrayList();
+ result.addAll(mapper(session).selectPurgeableSnapshotsWithEvents(resourceId));
+ result.addAll(mapper(session).selectPurgeableSnapshotsWithoutEvents(resourceId));
+ // sort by date
+ Collections.sort(result);
+ return result;
+ }
+
+ public PurgeDao deleteResourceTree(IdUuidPair rootIdUuid) {
+ DbSession session = mybatis.openSession(true);
+ try {
+ return deleteResourceTree(session, rootIdUuid);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public PurgeDao deleteResourceTree(DbSession session, IdUuidPair rootIdUuid) {
+ deleteProject(rootIdUuid, mapper(session), new PurgeCommands(session, profiler));
+ deleteFileSources(rootIdUuid.getUuid(), new PurgeCommands(session, profiler));
+ return this;
+ }
+
+ private static void deleteFileSources(String rootUuid, PurgeCommands commands) {
+ commands.deleteFileSources(rootUuid);
+ }
+
+ private static void deleteProject(IdUuidPair rootProjectId, PurgeMapper mapper, PurgeCommands commands) {
+ List<IdUuidPair> childrenIdUuid = mapper.selectProjectIdUuidsByRootId(rootProjectId.getId());
+ for (IdUuidPair childId : childrenIdUuid) {
+ deleteProject(childId, mapper, commands);
+ }
+
+ List<IdUuidPair> componentIdUuids = mapper.selectComponentIdUuidsByRootId(rootProjectId.getId());
+ commands.deleteResources(componentIdUuids);
+ }
+
+ private void disableResource(IdUuidPair componentIdUuid, PurgeMapper mapper) {
+ long componentId = componentIdUuid.getId();
+ mapper.deleteResourceIndex(Arrays.asList(componentId));
+ mapper.setSnapshotIsLastToFalse(componentId);
+ mapper.deleteFileSourcesByUuid(componentIdUuid.getUuid());
+ mapper.disableResource(componentId);
+ mapper.resolveResourceIssuesNotAlreadyResolved(componentIdUuid.getUuid(), system2.now());
+ }
+
+ public PurgeDao deleteSnapshots(PurgeSnapshotQuery query) {
+ final DbSession session = mybatis.openSession(true);
+ try {
+ return deleteSnapshots(query, session);
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public PurgeDao deleteSnapshots(PurgeSnapshotQuery query, final DbSession session) {
+ new PurgeCommands(session, profiler).deleteSnapshots(query);
+ return this;
+ }
+
+ /**
+ * Load the whole tree of projects, including the project given in parameter.
+ */
+ private List<ResourceDto> getProjects(long rootProjectId, SqlSession session) {
+ List<ResourceDto> projects = Lists.newArrayList();
+ projects.add(resourceDao.getResource(rootProjectId, session));
+ projects.addAll(resourceDao.getDescendantProjects(rootProjectId, session));
+ return projects;
+ }
+
+ public List<String> selectPurgeableFiles(DbSession dbSession, Long projectId) {
+ return mapper(dbSession).selectPurgeableFileUuids(projectId);
+ }
+
+ private PurgeMapper mapper(DbSession session) {
+ return session.getMapper(PurgeMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeListener.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeListener.java
new file mode 100644
index 00000000000..e1e90ccf714
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeListener.java
@@ -0,0 +1,33 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge;
+
+public interface PurgeListener {
+
+ PurgeListener EMPTY = new PurgeListener() {
+ @Override
+ public void onComponentDisabling(String uuid) {
+ // do nothing
+ }
+ };
+
+ void onComponentDisabling(String uuid);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java
new file mode 100644
index 00000000000..b1f84d0925f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeMapper.java
@@ -0,0 +1,95 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+public interface PurgeMapper {
+
+ List<Long> selectSnapshotIds(PurgeSnapshotQuery query);
+
+ List<Long> selectSnapshotIdsByResource(@Param("resourceIds") List<Long> resourceIds);
+
+ List<IdUuidPair> selectProjectIdUuidsByRootId(long rootResourceId);
+
+ List<IdUuidPair> selectComponentIdUuidsByRootId(long rootProjectId);
+
+ void deleteSnapshot(@Param("snapshotIds") List<Long> snapshotIds);
+
+ void deleteSnapshotDuplications(@Param("snapshotIds") List<Long> snapshotIds);
+
+ void deleteSnapshotEvents(@Param("snapshotIds") List<Long> snapshotIds);
+
+ void deleteSnapshotMeasures(@Param("snapshotIds") List<Long> snapshotIds);
+
+ List<Long> selectMetricIdsWithoutHistoricalData();
+
+ void deleteSnapshotWastedMeasures(@Param("snapshotIds") List<Long> snapshotIds, @Param("mids") List<Long> metricIds);
+
+ void updatePurgeStatusToOne(long snapshotId);
+
+ void disableResource(long resourceId);
+
+ void resolveResourceIssuesNotAlreadyResolved(@Param("componentUuid") String componentUuid, @Param("dateAsLong") Long dateAsLong);
+
+ void deleteResourceIndex(@Param("resourceIds") List<Long> resourceIds);
+
+ void deleteEvent(long eventId);
+
+ void setSnapshotIsLastToFalse(long resourceId);
+
+ void deleteResourceLinks(@Param("componentUuids") List<String> componentUuids);
+
+ void deleteResourceProperties(@Param("resourceIds") List<Long> resourceIds);
+
+ void deleteResource(@Param("resourceIds") List<Long> resourceIds);
+
+ void deleteResourceGroupRoles(@Param("resourceIds") List<Long> resourceIds);
+
+ void deleteResourceUserRoles(@Param("resourceIds") List<Long> resourceIds);
+
+ void deleteResourceManualMeasures(@Param("componentUuids") List<String> componentUuids);
+
+ void deleteComponentEvents(@Param("componentUuids") List<String> componentUuids);
+
+ void deleteResourceActionPlans(@Param("resourceIds") List<Long> resourceIds);
+
+ void deleteAuthors(@Param("resourceIds") List<Long> resourceIds);
+
+ List<PurgeableSnapshotDto> selectPurgeableSnapshotsWithEvents(long resourceId);
+
+ List<PurgeableSnapshotDto> selectPurgeableSnapshotsWithoutEvents(long resourceId);
+
+ void deleteComponentIssueChanges(@Param("componentUuids") List<String> componentUuids);
+
+ void deleteComponentIssues(@Param("componentUuids") List<String> componentUuids);
+
+ void deleteOldClosedIssueChanges(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate);
+
+ void deleteOldClosedIssues(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate);
+
+ void deleteFileSourcesByProjectUuid(String rootProjectUuid);
+
+ void deleteFileSourcesByUuid(String fileUuid);
+
+ List<String> selectPurgeableFileUuids(Long projectId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeProfiler.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeProfiler.java
new file mode 100644
index 00000000000..c68c8dfa2cc
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeProfiler.java
@@ -0,0 +1,104 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import com.google.common.annotations.VisibleForTesting;
+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.Map.Entry;
+import org.sonar.api.utils.TimeUtils;
+import org.sonar.api.utils.log.Logger;
+
+public class PurgeProfiler {
+
+ private Map<String, Long> durations = new HashMap<>();
+ private long startTime;
+ private String currentTable;
+ private final Clock clock;
+
+ public PurgeProfiler() {
+ this(new Clock());
+ }
+
+ @VisibleForTesting
+ PurgeProfiler(Clock clock) {
+ this.clock = clock;
+ }
+
+ public void reset() {
+ durations.clear();
+ }
+
+ void start(String table) {
+ this.startTime = clock.now();
+ this.currentTable = table;
+ }
+
+ void stop() {
+ final Long cumulatedDuration;
+ if (durations.containsKey(currentTable)) {
+ cumulatedDuration = durations.get(currentTable);
+ } else {
+ cumulatedDuration = 0L;
+ }
+ durations.put(currentTable, cumulatedDuration + (clock.now() - startTime));
+ }
+
+ public void dump(long totalTime, Logger logger) {
+ List<Entry<String, Long>> data = new ArrayList<>(durations.entrySet());
+ Collections.sort(data, new Comparator<Entry<String, Long>>() {
+ @Override
+ public int compare(Entry<String, Long> o1, Entry<String, Long> o2) {
+ return o2.getValue().compareTo(o1.getValue());
+ }
+ });
+ double percent = totalTime / 100.0;
+ for (Entry<String, Long> entry : truncateList(data)) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" o ").append(entry.getKey()).append(": ").append(TimeUtils.formatDuration(entry.getValue()))
+ .append(" (").append((int) (entry.getValue() / percent)).append("%)");
+ logger.info(sb.toString());
+ }
+ }
+
+ private List<Entry<String, Long>> truncateList(List<Entry<String, Long>> sortedFullList) {
+ int maxSize = 10;
+ List<Entry<String, Long>> result = new ArrayList<>(maxSize);
+ int i = 0;
+ for (Entry<String, Long> item : sortedFullList) {
+ if (i++ >= maxSize || item.getValue() == 0) {
+ return result;
+ }
+ result.add(item);
+ }
+ return result;
+ }
+
+ static class Clock {
+ public long now() {
+ return System.currentTimeMillis();
+ }
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeSnapshotQuery.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeSnapshotQuery.java
new file mode 100644
index 00000000000..2e44e1033ee
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeSnapshotQuery.java
@@ -0,0 +1,130 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+public final class PurgeSnapshotQuery {
+ private Long id;
+ private Long rootProjectId;
+ private Long rootSnapshotId;
+ private Long resourceId;
+ private String[] scopes;
+ private String[] qualifiers;
+ private String[] status;
+ private Boolean islast;
+ private Boolean notPurged;
+ private Boolean withVersionEvent;
+
+ private PurgeSnapshotQuery() {
+ }
+
+ public static PurgeSnapshotQuery create() {
+ return new PurgeSnapshotQuery();
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public PurgeSnapshotQuery setId(Long l) {
+ this.id = l;
+ return this;
+ }
+
+ public Long getRootProjectId() {
+ return rootProjectId;
+ }
+
+ public PurgeSnapshotQuery setRootProjectId(Long rootProjectId) {
+ this.rootProjectId = rootProjectId;
+ return this;
+ }
+
+ public String[] getScopes() {
+ return scopes;// NOSONAR May expose internal representation by returning reference to mutable object
+ }
+
+ public PurgeSnapshotQuery setScopes(String[] scopes) {
+ this.scopes = scopes; // NOSONAR May expose internal representation by incorporating reference to mutable object
+ return this;
+ }
+
+ public String[] getQualifiers() {
+ return qualifiers;// NOSONAR May expose internal representation by returning reference to mutable object
+ }
+
+ public PurgeSnapshotQuery setQualifiers(String[] qualifiers) {
+ this.qualifiers = qualifiers;// NOSONAR May expose internal representation by incorporating reference to mutable object
+ return this;
+ }
+
+ public String[] getStatus() {
+ return status;// NOSONAR May expose internal representation by returning reference to mutable object
+ }
+
+ public PurgeSnapshotQuery setStatus(String[] status) {
+ this.status = status; // NOSONAR org.sonar.db.purge.PurgeSnapshotQuery.setStatus(String[]) may expose internal representation
+ return this;
+ }
+
+ public Boolean getIslast() {
+ return islast;
+ }
+
+ public PurgeSnapshotQuery setIslast(Boolean islast) {
+ this.islast = islast;
+ return this;
+ }
+
+ public Boolean getNotPurged() {
+ return notPurged;
+ }
+
+ public PurgeSnapshotQuery setNotPurged(Boolean notPurged) {
+ this.notPurged = notPurged;
+ return this;
+ }
+
+ public Long getRootSnapshotId() {
+ return rootSnapshotId;
+ }
+
+ public PurgeSnapshotQuery setRootSnapshotId(Long rootSnapshotId) {
+ this.rootSnapshotId = rootSnapshotId;
+ return this;
+ }
+
+ public Long getResourceId() {
+ return resourceId;
+ }
+
+ public PurgeSnapshotQuery setResourceId(Long l) {
+ this.resourceId = l;
+ return this;
+ }
+
+ public Boolean getWithVersionEvent() {
+ return withVersionEvent;
+ }
+
+ public PurgeSnapshotQuery setWithVersionEvent(Boolean withVersionEvent) {
+ this.withVersionEvent = withVersionEvent;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/PurgeableSnapshotDto.java b/sonar-db/src/main/java/org/sonar/db/purge/PurgeableSnapshotDto.java
new file mode 100644
index 00000000000..b879738b04d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/PurgeableSnapshotDto.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import java.util.Date;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class PurgeableSnapshotDto implements Comparable<PurgeableSnapshotDto> {
+ private Date date;
+ private long snapshotId;
+ private boolean hasEvents;
+ private boolean isLast;
+
+ public Date getDate() {
+ return date;
+ }
+
+ public long getSnapshotId() {
+ return snapshotId;
+ }
+
+ public boolean hasEvents() {
+ return hasEvents;
+ }
+
+ public boolean isLast() {
+ return isLast;
+ }
+
+ public PurgeableSnapshotDto setDate(Long aLong) {
+ this.date = new Date(aLong);
+ return this;
+ }
+
+ public PurgeableSnapshotDto setSnapshotId(long snapshotId) {
+ this.snapshotId = snapshotId;
+ return this;
+ }
+
+ public PurgeableSnapshotDto setHasEvents(boolean b) {
+ this.hasEvents = b;
+ return this;
+ }
+
+ public PurgeableSnapshotDto setLast(boolean last) {
+ isLast = last;
+ return this;
+ }
+
+ @Override
+ public int compareTo(PurgeableSnapshotDto other) {
+ return date.compareTo(other.date);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PurgeableSnapshotDto that = (PurgeableSnapshotDto) o;
+ return snapshotId == that.snapshotId;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) snapshotId;
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this, ToStringStyle.SIMPLE_STYLE).toString();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/package-info.java b/sonar-db/src/main/java/org/sonar/db/purge/package-info.java
new file mode 100644
index 00000000000..890b581cc1c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.purge;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/DefaultPeriodCleaner.java b/sonar-db/src/main/java/org/sonar/db/purge/period/DefaultPeriodCleaner.java
new file mode 100644
index 00000000000..ac7bad54d1a
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/DefaultPeriodCleaner.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.List;
+import org.sonar.api.config.Settings;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbSession;
+import org.sonar.db.purge.PurgeDao;
+import org.sonar.db.purge.PurgeSnapshotQuery;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+@ServerSide
+public class DefaultPeriodCleaner {
+
+ private static final Logger LOG = Loggers.get(DefaultPeriodCleaner.class);
+ private PurgeDao purgeDao;
+
+ public DefaultPeriodCleaner(PurgeDao purgeDao) {
+ this.purgeDao = purgeDao;
+ }
+
+ public void clean(DbSession session, long projectId, Settings settings) {
+ doClean(projectId, new Filters(settings).all(), session);
+ }
+
+ @VisibleForTesting
+ void doClean(long projectId, List<Filter> filters, DbSession session) {
+ List<PurgeableSnapshotDto> history = selectProjectSnapshots(projectId, session);
+ for (Filter filter : filters) {
+ filter.log();
+ delete(filter.filter(history), session);
+ }
+ }
+
+ private void delete(List<PurgeableSnapshotDto> snapshots, DbSession session) {
+ for (PurgeableSnapshotDto snapshot : snapshots) {
+ LOG.debug("<- Delete snapshot: {} [{}]", DateUtils.formatDateTime(snapshot.getDate()), snapshot.getSnapshotId());
+ purgeDao.deleteSnapshots(PurgeSnapshotQuery.create().setRootSnapshotId(snapshot.getSnapshotId()), session);
+ purgeDao.deleteSnapshots(PurgeSnapshotQuery.create().setId(snapshot.getSnapshotId()), session);
+ }
+ }
+
+ private List<PurgeableSnapshotDto> selectProjectSnapshots(long resourceId, DbSession session) {
+ return purgeDao.selectPurgeableSnapshots(resourceId, session);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/DeleteAllFilter.java b/sonar-db/src/main/java/org/sonar/db/purge/period/DeleteAllFilter.java
new file mode 100644
index 00000000000..9cd9275eaad
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/DeleteAllFilter.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import com.google.common.collect.Lists;
+import java.util.Date;
+import java.util.List;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+class DeleteAllFilter implements Filter {
+ private final Date before;
+
+ public DeleteAllFilter(Date before) {
+ this.before = before;
+ }
+
+ @Override
+ public List<PurgeableSnapshotDto> filter(List<PurgeableSnapshotDto> history) {
+ List<PurgeableSnapshotDto> result = Lists.newArrayList();
+ for (PurgeableSnapshotDto snapshot : history) {
+ if (snapshot.getDate().before(before)) {
+ result.add(snapshot);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void log() {
+ Loggers.get(getClass()).debug("-> Delete data prior to: {}", DateUtils.formatDate(before));
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/Filter.java b/sonar-db/src/main/java/org/sonar/db/purge/period/Filter.java
new file mode 100644
index 00000000000..b10fb4cd342
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/Filter.java
@@ -0,0 +1,30 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import java.util.List;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+interface Filter {
+ List<PurgeableSnapshotDto> filter(List<PurgeableSnapshotDto> snapshots);
+
+ void log();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/Filters.java b/sonar-db/src/main/java/org/sonar/db/purge/period/Filters.java
new file mode 100644
index 00000000000..08b9bf04648
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/Filters.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import com.google.common.collect.Lists;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang.time.DateUtils;
+import org.sonar.api.config.Settings;
+import org.sonar.core.config.PurgeConstants;
+
+class Filters {
+ private final List<Filter> all = Lists.newArrayList();
+
+ Filters(Settings settings) {
+ Date dateToStartKeepingOneSnapshotByDay = getDateFromHours(settings, PurgeConstants.HOURS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_DAY);
+ Date dateToStartKeepingOneSnapshotByWeek = getDateFromWeeks(settings, PurgeConstants.WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_WEEK);
+ Date dateToStartKeepingOneSnapshotByMonth = getDateFromWeeks(settings, PurgeConstants.WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_MONTH);
+ Date dateToStartDeletingAllSnapshots = getDateFromWeeks(settings, PurgeConstants.WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS);
+
+ all.add(new KeepOneFilter(dateToStartKeepingOneSnapshotByWeek, dateToStartKeepingOneSnapshotByDay, Calendar.DAY_OF_YEAR, "day"));
+ all.add(new KeepOneFilter(dateToStartKeepingOneSnapshotByMonth, dateToStartKeepingOneSnapshotByWeek, Calendar.WEEK_OF_YEAR, "week"));
+ all.add(new KeepOneFilter(dateToStartDeletingAllSnapshots, dateToStartKeepingOneSnapshotByMonth, Calendar.MONTH, "month"));
+ all.add(new DeleteAllFilter(dateToStartDeletingAllSnapshots));
+ }
+
+ static Date getDateFromWeeks(Settings settings, String propertyKey) {
+ int weeks = settings.getInt(propertyKey);
+ return DateUtils.addWeeks(new Date(), -weeks);
+ }
+
+ static Date getDateFromHours(Settings settings, String propertyKey) {
+ int hours = settings.getInt(propertyKey);
+ return DateUtils.addHours(new Date(), -hours);
+ }
+
+ List<Filter> all() {
+ return all;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/Interval.java b/sonar-db/src/main/java/org/sonar/db/purge/period/Interval.java
new file mode 100644
index 00000000000..2014d3fbecc
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/Interval.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import com.google.common.collect.Lists;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import org.apache.commons.lang.time.DateUtils;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+final class Interval {
+ List<PurgeableSnapshotDto> snapshots = Lists.newArrayList();
+
+ void add(PurgeableSnapshotDto snapshot) {
+ snapshots.add(snapshot);
+ }
+
+ List<PurgeableSnapshotDto> get() {
+ return snapshots;
+ }
+
+ int count() {
+ return snapshots.size();
+ }
+
+ static List<Interval> group(List<PurgeableSnapshotDto> snapshots, Date start, Date end, int calendarField) {
+ List<Interval> intervals = Lists.newArrayList();
+
+ GregorianCalendar calendar = new GregorianCalendar();
+ int lastYear = -1;
+ int lastFieldValue = -1;
+ Interval currentInterval = null;
+
+ for (PurgeableSnapshotDto snapshot : snapshots) {
+ if (!DateUtils.isSameDay(start, snapshot.getDate()) && snapshot.getDate().after(start) &&
+ (snapshot.getDate().before(end) || DateUtils.isSameDay(end, snapshot.getDate()))) {
+ calendar.setTime(snapshot.getDate());
+ int currentFieldValue = calendar.get(calendarField);
+ int currentYear = calendar.get(Calendar.YEAR);
+ if (lastYear != currentYear || lastFieldValue != currentFieldValue) {
+ currentInterval = new Interval();
+ intervals.add(currentInterval);
+ }
+ lastFieldValue = currentFieldValue;
+ lastYear = currentYear;
+ if (currentInterval != null) {
+ currentInterval.add(snapshot);
+ }
+ }
+ }
+ return intervals;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/KeepOneFilter.java b/sonar-db/src/main/java/org/sonar/db/purge/period/KeepOneFilter.java
new file mode 100644
index 00000000000..7335e7c1981
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/KeepOneFilter.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import java.util.Date;
+import java.util.List;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+class KeepOneFilter implements Filter {
+
+ private final Date start;
+ private final Date end;
+ private final int dateField;
+ private final String label;
+
+ KeepOneFilter(Date start, Date end, int calendarField, String label) {
+ this.start = start;
+ this.end = end;
+ this.dateField = calendarField;
+ this.label = label;
+ }
+
+ @Override
+ public List<PurgeableSnapshotDto> filter(List<PurgeableSnapshotDto> history) {
+ List<Interval> intervals = Interval.group(history, start, end, dateField);
+ List<PurgeableSnapshotDto> result = Lists.newArrayList();
+ for (Interval interval : intervals) {
+ appendSnapshotsToDelete(interval, result);
+ }
+
+ return result;
+ }
+
+ @Override
+ public void log() {
+ Loggers.get(getClass()).debug("-> Keep one snapshot per {} between {} and {}", label, DateUtils.formatDate(start), DateUtils.formatDate(end));
+ }
+
+ private void appendSnapshotsToDelete(Interval interval, List<PurgeableSnapshotDto> toDelete) {
+ if (interval.count() > 1) {
+ List<PurgeableSnapshotDto> deletables = Lists.newArrayList();
+ List<PurgeableSnapshotDto> toKeep = Lists.newArrayList();
+ for (PurgeableSnapshotDto snapshot : interval.get()) {
+ if (isDeletable(snapshot)) {
+ deletables.add(snapshot);
+ } else {
+ toKeep.add(snapshot);
+ }
+ }
+
+ if (!toKeep.isEmpty()) {
+ toDelete.addAll(deletables);
+
+ } else if (deletables.size() > 1) {
+ // keep one snapshot
+ toDelete.addAll(deletables.subList(1, deletables.size()));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static boolean isDeletable(PurgeableSnapshotDto snapshot) {
+ return !snapshot.isLast() && !snapshot.hasEvents();
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/purge/period/package-info.java b/sonar-db/src/main/java/org/sonar/db/purge/period/package-info.java
new file mode 100644
index 00000000000..462f8f07e52
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/purge/period/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.purge.period;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociation.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociation.java
new file mode 100644
index 00000000000..5aafbde3669
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociation.java
@@ -0,0 +1,79 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class ProjectQgateAssociation {
+
+ private Long id;
+ private String name;
+ private boolean isMember;
+
+ public Long id() {
+ return id;
+ }
+
+ public ProjectQgateAssociation setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public ProjectQgateAssociation setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public boolean isMember() {
+ return isMember;
+ }
+
+ public ProjectQgateAssociation setMember(boolean isMember) {
+ this.isMember = isMember;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ProjectQgateAssociation that = (ProjectQgateAssociation) o;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java
new file mode 100644
index 00000000000..e5c03caa515
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualitygate;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+public class ProjectQgateAssociationDao {
+
+ private final MyBatis mybatis;
+
+ public ProjectQgateAssociationDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public List<ProjectQgateAssociationDto> selectProjects(ProjectQgateAssociationQuery query, Long gateId, int offset, int limit) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ Map<String, Object> params = ImmutableMap.of("query", query, "gateId", gateId.toString());
+ return mapper(session).selectProjects(params, new RowBounds(offset, limit));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @VisibleForTesting
+ List<ProjectQgateAssociationDto> selectProjects(ProjectQgateAssociationQuery query, Long gateId) {
+ return selectProjects(query, gateId, 0, Integer.MAX_VALUE);
+ }
+
+ private ProjectQgateAssociationMapper mapper(SqlSession session) {
+ return session.getMapper(ProjectQgateAssociationMapper.class);
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java
new file mode 100644
index 00000000000..18b3d7f0e19
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * @since 4.3
+ */
+public class ProjectQgateAssociationDto {
+
+ private Long id;
+ private String name;
+ private String gateId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public ProjectQgateAssociationDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ProjectQgateAssociationDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getGateId() {
+ return gateId;
+ }
+
+ public ProjectQgateAssociationDto setGateId(@Nullable String gateId) {
+ this.gateId = gateId;
+ return this;
+ }
+
+ public ProjectQgateAssociation toQgateAssociation() {
+ return new ProjectQgateAssociation()
+ .setId(id)
+ .setName(name)
+ .setMember(gateId != null);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java
new file mode 100644
index 00000000000..1472fe7199a
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.session.RowBounds;
+
+public interface ProjectQgateAssociationMapper {
+
+ List<ProjectQgateAssociationDto> selectProjects(ProjectQgateAssociationQuery query);
+
+ List<ProjectQgateAssociationDto> selectProjects(Map<String, Object> params, RowBounds rowBounds);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationQuery.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationQuery.java
new file mode 100644
index 00000000000..7a96fe57b05
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationQuery.java
@@ -0,0 +1,168 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+public class ProjectQgateAssociationQuery {
+
+ public static final int DEFAULT_PAGE_INDEX = 1;
+ public static final int DEFAULT_PAGE_SIZE = 100;
+
+ public static final String ANY = "all";
+ public static final String IN = "selected";
+ public static final String OUT = "deselected";
+ public static final Set<String> AVAILABLE_MEMBERSHIP = ImmutableSet.of(ANY, IN, OUT);
+
+ private final String gateId;
+ private final String membership;
+
+ private final String projectSearch;
+
+ // for internal use in MyBatis
+ final String projectSearchSql;
+
+ // max results per page
+ private final int pageSize;
+
+ // index of selected page. Start with 1.
+ private final int pageIndex;
+
+ private ProjectQgateAssociationQuery(Builder builder) {
+ this.gateId = builder.gateId;
+ this.membership = builder.membership;
+ this.projectSearch = builder.projectSearch;
+ this.projectSearchSql = projectSearchToSql(projectSearch);
+
+ this.pageSize = builder.pageSize;
+ this.pageIndex = builder.pageIndex;
+ }
+
+ private String projectSearchToSql(@Nullable String s) {
+ String sql = null;
+ if (s != null) {
+ sql = StringUtils.replace(StringUtils.lowerCase(s), "%", "/%");
+ sql = StringUtils.replace(sql, "_", "/_");
+ sql = sql + "%";
+ }
+ return sql;
+ }
+
+ public String gateId() {
+ return gateId;
+ }
+
+ @CheckForNull
+ public String membership() {
+ return membership;
+ }
+
+ /**
+ * Search for projects containing a given string
+ */
+ @CheckForNull
+ public String projectSearch() {
+ return projectSearch;
+ }
+
+ public int pageSize() {
+ return pageSize;
+ }
+
+ public int pageIndex() {
+ return pageIndex;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String gateId;
+ private String membership;
+ private String projectSearch;
+
+ private Integer pageIndex = DEFAULT_PAGE_INDEX;
+ private Integer pageSize = DEFAULT_PAGE_SIZE;
+
+ private Builder() {
+ }
+
+ public Builder gateId(String gateId) {
+ this.gateId = gateId;
+ return this;
+ }
+
+ public Builder membership(@Nullable String membership) {
+ this.membership = membership;
+ return this;
+ }
+
+ public Builder projectSearch(@Nullable String s) {
+ this.projectSearch = StringUtils.defaultIfBlank(s, null);
+ return this;
+ }
+
+ public Builder pageSize(@Nullable Integer i) {
+ this.pageSize = i;
+ return this;
+ }
+
+ public Builder pageIndex(@Nullable Integer i) {
+ this.pageIndex = i;
+ return this;
+ }
+
+ private void initMembership() {
+ if (membership == null) {
+ membership = ProjectQgateAssociationQuery.ANY;
+ } else {
+ Preconditions.checkArgument(AVAILABLE_MEMBERSHIP.contains(membership),
+ "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIP);
+ }
+ }
+
+ private void initPageSize() {
+ if (pageSize == null) {
+ pageSize = DEFAULT_PAGE_SIZE;
+ }
+ }
+
+ private void initPageIndex() {
+ if (pageIndex == null) {
+ pageIndex = DEFAULT_PAGE_INDEX;
+ }
+ Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
+ }
+
+ public ProjectQgateAssociationQuery build() {
+ Preconditions.checkNotNull(gateId, "Gate ID cant be null.");
+ initMembership();
+ initPageIndex();
+ initPageSize();
+ return new ProjectQgateAssociationQuery(this);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java
new file mode 100644
index 00000000000..df5f6886f25
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDao.java
@@ -0,0 +1,124 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import java.util.Collection;
+import java.util.Date;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.MyBatis;
+
+/**
+ * @since 4.3
+ */
+public class QualityGateConditionDao implements Dao {
+
+ private final MyBatis myBatis;
+
+ public QualityGateConditionDao(MyBatis myBatis) {
+ this.myBatis = myBatis;
+ }
+
+ public void insert(QualityGateConditionDto newQualityGate) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ insert(newQualityGate, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(QualityGateConditionDto newQualityGate, SqlSession session) {
+ getMapper(session).insert(newQualityGate.setCreatedAt(new Date()));
+ }
+
+ public Collection<QualityGateConditionDto> selectForQualityGate(long qGateId) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ return selectForQualityGate(qGateId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Collection<QualityGateConditionDto> selectForQualityGate(long qGateId, SqlSession session) {
+ return getMapper(session).selectForQualityGate(qGateId);
+ }
+
+ public QualityGateConditionDto selectById(long id) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ return selectById(id, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public QualityGateConditionDto selectById(long id, SqlSession session) {
+ return getMapper(session).selectById(id);
+ }
+
+ public void delete(QualityGateConditionDto qGate) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ delete(qGate, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(QualityGateConditionDto qGate, SqlSession session) {
+ getMapper(session).delete(qGate.getId());
+ }
+
+ public void update(QualityGateConditionDto qGate) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ update(qGate, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void update(QualityGateConditionDto qGate, SqlSession session) {
+ getMapper(session).update(qGate.setUpdatedAt(new Date()));
+ }
+
+ public void deleteConditionsWithInvalidMetrics() {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ deleteConditionsWithInvalidMetrics(session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void deleteConditionsWithInvalidMetrics(SqlSession session) {
+ getMapper(session).deleteConditionsWithInvalidMetrics();
+ }
+
+ private QualityGateConditionMapper getMapper(SqlSession session) {
+ return session.getMapper(QualityGateConditionMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java
new file mode 100644
index 00000000000..dbfb9654d64
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionDto.java
@@ -0,0 +1,208 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.measures.Metric.ValueType;
+
+/**
+ * @since 4.3
+ */
+public class QualityGateConditionDto {
+
+ public static final String OPERATOR_EQUALS = "EQ";
+
+ public static final String OPERATOR_NOT_EQUALS = "NE";
+
+ public static final String OPERATOR_GREATER_THAN = "GT";
+
+ public static final String OPERATOR_LESS_THAN = "LT";
+
+ public static final List<String> ALL_OPERATORS = ImmutableList.of(
+ OPERATOR_LESS_THAN,
+ OPERATOR_GREATER_THAN,
+ OPERATOR_EQUALS,
+ OPERATOR_NOT_EQUALS
+ );
+
+ private static final List<String> NUMERIC_OPERATORS = ImmutableList.of(
+ OPERATOR_LESS_THAN,
+ OPERATOR_GREATER_THAN,
+ OPERATOR_EQUALS,
+ OPERATOR_NOT_EQUALS
+ );
+ private static final List<String> STRING_OPERATORS = ImmutableList.of(
+ OPERATOR_EQUALS,
+ OPERATOR_NOT_EQUALS,
+ OPERATOR_LESS_THAN,
+ OPERATOR_GREATER_THAN
+ );
+ private static final List<String> LEVEL_OPERATORS = ImmutableList.of(
+ OPERATOR_EQUALS,
+ OPERATOR_NOT_EQUALS
+ );
+ private static final List<String> BOOLEAN_OPERATORS = ImmutableList.of(
+ OPERATOR_EQUALS
+ );
+
+ private static final Map<ValueType, List<String>> OPERATORS_BY_TYPE = ImmutableMap.<ValueType, List<String>>builder()
+ .put(ValueType.BOOL, BOOLEAN_OPERATORS)
+ .put(ValueType.LEVEL, LEVEL_OPERATORS)
+ .put(ValueType.STRING, STRING_OPERATORS)
+ .put(ValueType.INT, NUMERIC_OPERATORS)
+ .put(ValueType.FLOAT, NUMERIC_OPERATORS)
+ .put(ValueType.PERCENT, NUMERIC_OPERATORS)
+ .put(ValueType.MILLISEC, NUMERIC_OPERATORS)
+ .put(ValueType.RATING, NUMERIC_OPERATORS)
+ .put(ValueType.WORK_DUR, NUMERIC_OPERATORS)
+ .build();
+
+ private long id;
+
+ private long qualityGateId;
+
+ private long metricId;
+
+ private String metricKey;
+
+ private Integer period;
+
+ private String operator;
+
+ private String warningThreshold;
+
+ private String errorThreshold;
+
+ private Date createdAt;
+
+ private Date updatedAt;
+
+ public long getId() {
+ return id;
+ }
+
+ public QualityGateConditionDto setId(long id) {
+ this.id = id;
+ return this;
+ }
+
+ public long getQualityGateId() {
+ return qualityGateId;
+ }
+
+ public QualityGateConditionDto setQualityGateId(long qualityGateId) {
+ this.qualityGateId = qualityGateId;
+ return this;
+ }
+
+ public long getMetricId() {
+ return metricId;
+ }
+
+ public QualityGateConditionDto setMetricId(long metricId) {
+ this.metricId = metricId;
+ return this;
+ }
+
+ @CheckForNull
+ public String getMetricKey() {
+ return metricKey;
+ }
+
+ public QualityGateConditionDto setMetricKey(String metricKey) {
+ this.metricKey = metricKey;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getPeriod() {
+ return period;
+ }
+
+ public QualityGateConditionDto setPeriod(@Nullable Integer period) {
+ this.period = period;
+ return this;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public QualityGateConditionDto setOperator(String operator) {
+ this.operator = operator;
+ return this;
+ }
+
+ public String getWarningThreshold() {
+ return warningThreshold;
+ }
+
+ public QualityGateConditionDto setWarningThreshold(@Nullable String warningThreshold) {
+ this.warningThreshold = warningThreshold;
+ return this;
+ }
+
+ public String getErrorThreshold() {
+ return errorThreshold;
+ }
+
+ public QualityGateConditionDto setErrorThreshold(@Nullable String errorThreshold) {
+ this.errorThreshold = errorThreshold;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public QualityGateConditionDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public QualityGateConditionDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public static boolean isOperatorAllowed(String operator, ValueType metricType) {
+ return getOperatorsForType(metricType).contains(operator);
+ }
+
+ public static Collection<String> getOperatorsForType(ValueType metricType) {
+ if (OPERATORS_BY_TYPE.containsKey(metricType)) {
+ return OPERATORS_BY_TYPE.get(metricType);
+ } else {
+ return Collections.emptySet();
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java
new file mode 100644
index 00000000000..da9527cbe61
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateConditionMapper.java
@@ -0,0 +1,37 @@
+package org.sonar.db.qualitygate;
+
+import java.util.List;
+
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+public interface QualityGateConditionMapper {
+
+ void insert(QualityGateConditionDto newCondition);
+
+ List<QualityGateConditionDto> selectForQualityGate(long qGateId);
+
+ void update(QualityGateConditionDto newCondition);
+
+ QualityGateConditionDto selectById(long id);
+
+ void delete(long id);
+
+ void deleteConditionsWithInvalidMetrics();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java
new file mode 100644
index 00000000000..5e483bbe30e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java
@@ -0,0 +1,122 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import java.util.Collection;
+import java.util.Date;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+/**
+ * @since 4.3
+ */
+public class QualityGateDao {
+
+ private final MyBatis myBatis;
+
+ public QualityGateDao(MyBatis myBatis) {
+ this.myBatis = myBatis;
+ }
+
+ public void insert(QualityGateDto newQualityGate) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ insert(newQualityGate, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insert(QualityGateDto newQualityGate, SqlSession session) {
+ getMapper(session).insert(newQualityGate.setCreatedAt(new Date()));
+ }
+
+ public Collection<QualityGateDto> selectAll() {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ return selectAll(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Collection<QualityGateDto> selectAll(SqlSession session) {
+ return getMapper(session).selectAll();
+ }
+
+ public QualityGateDto selectByName(String name) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ return selectByName(name, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public QualityGateDto selectByName(String name, SqlSession session) {
+ return getMapper(session).selectByName(name);
+ }
+
+ public QualityGateDto selectById(long id) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ return selectById(id, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public QualityGateDto selectById(long id, SqlSession session) {
+ return getMapper(session).selectById(id);
+ }
+
+ public void delete(QualityGateDto qGate) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ delete(qGate, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(QualityGateDto qGate, SqlSession session) {
+ getMapper(session).delete(qGate.getId());
+ }
+
+ public void update(QualityGateDto qGate) {
+ SqlSession session = myBatis.openSession(false);
+ try {
+ update(qGate, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void update(QualityGateDto qGate, SqlSession session) {
+ getMapper(session).update(qGate.setUpdatedAt(new Date()));
+ }
+
+ private QualityGateMapper getMapper(SqlSession session) {
+ return session.getMapper(QualityGateMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDto.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDto.java
new file mode 100644
index 00000000000..e0e0f083ba3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateDto.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import java.util.Date;
+
+/**
+ * @since 4.3
+ */
+public class QualityGateDto {
+
+ private Long id;
+
+ private String name;
+
+ private Date createdAt;
+
+ private Date updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public QualityGateDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public QualityGateDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public QualityGateDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public QualityGateDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java
new file mode 100644
index 00000000000..c4880835cbe
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java
@@ -0,0 +1,37 @@
+package org.sonar.db.qualitygate;
+
+import java.util.List;
+
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+public interface QualityGateMapper {
+
+ void insert(QualityGateDto newQualityGate);
+
+ List<QualityGateDto> selectAll();
+
+ QualityGateDto selectByName(String name);
+
+ QualityGateDto selectById(long id);
+
+ void delete(long id);
+
+ void update(QualityGateDto qGate);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java
new file mode 100644
index 00000000000..24efe187dd7
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+/**
+ * @deprecated use the ActiveRuleDao class defined in sonar-server
+ */
+@Deprecated
+@ServerSide
+public class ActiveRuleDao {
+
+ private final MyBatis mybatis;
+
+ public ActiveRuleDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public void insert(ActiveRuleDto dto, SqlSession session) {
+ session.getMapper(ActiveRuleMapper.class).insert(dto);
+ }
+
+ public List<ActiveRuleDto> selectByProfileKey(String profileKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ActiveRuleMapper.class).selectByProfileKey(profileKey);
+ } finally {
+ session.close();
+ }
+ }
+
+ public void insert(ActiveRuleParamDto dto, SqlSession session) {
+ session.getMapper(ActiveRuleMapper.class).insertParameter(dto);
+ }
+
+ public List<ActiveRuleParamDto> selectParamsByProfileKey(String profileKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return session.getMapper(ActiveRuleMapper.class).selectParamsByProfileKey(profileKey);
+ } finally {
+ session.close();
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
new file mode 100644
index 00000000000..ab2d37f9e84
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
@@ -0,0 +1,159 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import com.google.common.base.Preconditions;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.core.rule.SeverityUtil;
+import org.sonar.db.Dto;
+import org.sonar.db.rule.RuleDto;
+
+public class ActiveRuleDto extends Dto<ActiveRuleKey> {
+
+ public static final String INHERITED = ActiveRule.INHERITED;
+ public static final String OVERRIDES = ActiveRule.OVERRIDES;
+
+ private String repository;
+ private String ruleField;
+ private String profileKey;
+
+ private Integer id;
+ private Integer profileId;
+ private Integer ruleId;
+ private Integer severity;
+ private String inheritance;
+
+ /**
+ * @deprecated for internal use, should be private
+ */
+ @Deprecated
+ public ActiveRuleDto setKey(ActiveRuleKey key) {
+ this.repository = key.ruleKey().repository();
+ this.ruleField = key.ruleKey().rule();
+ this.profileKey = key.qProfile();
+ return this;
+ }
+
+ @Override
+ public ActiveRuleKey getKey() {
+ return ActiveRuleKey.of(profileKey, RuleKey.of(repository, ruleField));
+ }
+
+ // This field do not exists in db, it's only retrieve by joins
+ private Integer parentId;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public ActiveRuleDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public Integer getProfileId() {
+ return profileId;
+ }
+
+ // TODO mark as private
+ public ActiveRuleDto setProfileId(Integer profileId) {
+ this.profileId = profileId;
+ return this;
+ }
+
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ // TODO mark as private
+ public ActiveRuleDto setRuleId(Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ public Integer getSeverity() {
+ return severity;
+ }
+
+ public String getSeverityString() {
+ return SeverityUtil.getSeverityFromOrdinal(severity);
+ }
+
+ public ActiveRuleDto setSeverity(Integer severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public ActiveRuleDto setSeverity(String severity) {
+ this.severity = SeverityUtil.getOrdinalFromSeverity(severity);
+ return this;
+ }
+
+ @CheckForNull
+ public String getInheritance() {
+ return inheritance;
+ }
+
+ public ActiveRuleDto setInheritance(@Nullable String inheritance) {
+ this.inheritance = inheritance;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getParentId() {
+ return parentId;
+ }
+
+ public ActiveRuleDto setParentId(@Nullable Integer parentId) {
+ this.parentId = parentId;
+ return this;
+ }
+
+ public boolean isInherited() {
+ return StringUtils.equals(INHERITED, inheritance);
+ }
+
+ public boolean doesOverride() {
+ return StringUtils.equals(OVERRIDES, inheritance);
+ }
+
+ public static ActiveRuleDto createFor(QualityProfileDto profileDto, RuleDto ruleDto) {
+ Preconditions.checkNotNull(profileDto.getId(), "Profile is not persisted");
+ Preconditions.checkNotNull(ruleDto.getId(), "Rule is not persisted");
+ ActiveRuleDto dto = new ActiveRuleDto();
+ dto.setProfileId(profileDto.getId());
+ dto.setRuleId(ruleDto.getId());
+ dto.setKey(ActiveRuleKey.of(profileDto.getKee(), ruleDto.getKey()));
+ return dto;
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java
new file mode 100644
index 00000000000..4527fabaf88
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleKey.java
@@ -0,0 +1,107 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import com.google.common.base.Preconditions;
+import java.io.Serializable;
+import org.sonar.api.rule.RuleKey;
+
+/**
+ *
+ * @since 4.4
+ */
+public class ActiveRuleKey implements Serializable {
+
+ private final String qualityProfileKey;
+ private final RuleKey ruleKey;
+
+ protected ActiveRuleKey(String qualityProfileKey, RuleKey ruleKey) {
+ this.qualityProfileKey = qualityProfileKey;
+ this.ruleKey = ruleKey;
+ }
+
+ /**
+ * Create a key. Parameters are NOT null.
+ */
+ public static ActiveRuleKey of(String qualityProfileKey, RuleKey ruleKey) {
+ Preconditions.checkNotNull(qualityProfileKey, "QProfile is missing");
+ Preconditions.checkNotNull(ruleKey, "RuleKey is missing");
+ return new ActiveRuleKey(qualityProfileKey, ruleKey);
+ }
+
+ /**
+ * Create a key from a string representation (see {@link #toString()}. An {@link IllegalArgumentException} is raised
+ * if the format is not valid.
+ */
+ public static ActiveRuleKey parse(String s) {
+ Preconditions.checkArgument(s.split(":").length >= 3, "Bad format of activeRule key: " + s);
+ int semiColonPos = s.indexOf(":");
+ String key = s.substring(0, semiColonPos);
+ String ruleKey = s.substring(semiColonPos + 1);
+ return ActiveRuleKey.of(key, RuleKey.parse(ruleKey));
+ }
+
+ /**
+ * Never null
+ */
+ public RuleKey ruleKey() {
+ return ruleKey;
+ }
+
+ /**
+ * Never null
+ */
+ public String qProfile() {
+ return qualityProfileKey;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ActiveRuleKey activeRuleKey = (ActiveRuleKey) o;
+ if (!qualityProfileKey.equals(activeRuleKey.qualityProfileKey)) {
+ return false;
+ }
+ if (!ruleKey.equals(activeRuleKey.ruleKey)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = qualityProfileKey.hashCode();
+ result = 31 * result + ruleKey.hashCode();
+ return result;
+ }
+
+ /**
+ * Format is "qprofile:rule", for example "12345:squid:AvoidCycle"
+ */
+ @Override
+ public String toString() {
+ return String.format("%s:%s", qualityProfileKey, ruleKey.toString());
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
new file mode 100644
index 00000000000..ee409f35171
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import java.sql.Timestamp;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+public interface ActiveRuleMapper {
+
+ void insert(ActiveRuleDto dto);
+
+ void update(ActiveRuleDto dto);
+
+ void delete(int activeRuleId);
+
+ @CheckForNull
+ ActiveRuleDto selectById(Integer id);
+
+ List<ActiveRuleDto> selectByRuleId(int ruleId);
+
+ List<ActiveRuleDto> selectByProfileKey(String key);
+
+ List<ActiveRuleDto> selectAll();
+
+ void insertParameter(ActiveRuleParamDto dto);
+
+ void updateParameter(ActiveRuleParamDto dto);
+
+ void deleteParameters(int activeRuleId);
+
+ void deleteParameter(int activeRuleParamId);
+
+ @CheckForNull
+ ActiveRuleParamDto selectParamByActiveRuleAndKey(@Param("activeRuleId") int activeRuleId, @Param("key") String key);
+
+ List<ActiveRuleParamDto> selectParamsByActiveRuleId(int activeRuleId);
+
+ List<ActiveRuleParamDto> selectParamsByProfileKey(String profileKey);
+
+ ActiveRuleDto selectByKey(@Param("profileKey") String profileKey,
+ @Param("repository") String repository, @Param("rule") String rule);
+
+ List<ActiveRuleParamDto> selectAllParams();
+
+ List<ActiveRuleDto> selectAfterDate(@Nullable Timestamp date);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleParamDto.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleParamDto.java
new file mode 100644
index 00000000000..6036f548c8c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ActiveRuleParamDto.java
@@ -0,0 +1,104 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import com.google.common.base.Preconditions;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.db.rule.RuleParamDto;
+
+public class ActiveRuleParamDto {
+
+ private Integer id;
+ private Integer activeRuleId;
+ private Integer rulesParameterId;
+ private String kee;
+ private String value;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public ActiveRuleParamDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public Integer getActiveRuleId() {
+ return activeRuleId;
+ }
+
+ public ActiveRuleParamDto setActiveRuleId(Integer activeRuleId) {
+ this.activeRuleId = activeRuleId;
+ return this;
+ }
+
+ public Integer getRulesParameterId() {
+ return rulesParameterId;
+ }
+
+ // TODO set private or drop
+ public ActiveRuleParamDto setRulesParameterId(Integer rulesParameterId) {
+ this.rulesParameterId = rulesParameterId;
+ return this;
+ }
+
+ public String getKey() {
+ return kee;
+ }
+
+ public ActiveRuleParamDto setKey(String key) {
+ this.kee = key;
+ return this;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public ActiveRuleParamDto setValue(String value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
+ }
+
+ public static ActiveRuleParamDto createFor(RuleParamDto param) {
+ Preconditions.checkArgument(param.getId() != null, "Parameter is not persisted");
+ return new ActiveRuleParamDto()
+ .setKey(param.getName())
+ .setRulesParameterId(param.getId());
+ }
+
+ public static Map<String, ActiveRuleParamDto> groupByKey(Collection<ActiveRuleParamDto> params) {
+ Map<String, ActiveRuleParamDto> result = new HashMap<>();
+ for (ActiveRuleParamDto param : params) {
+ result.put(param.getKey(), param);
+ }
+ return result;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/ProjectQprofileAssociationDto.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ProjectQprofileAssociationDto.java
new file mode 100644
index 00000000000..cb0c8e205bb
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/ProjectQprofileAssociationDto.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import javax.annotation.CheckForNull;
+
+public class ProjectQprofileAssociationDto {
+
+ private Long projectId;
+ private String projectUuid;
+ private String projectName;
+ private String profileKey;
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ @CheckForNull
+ public String getProfileKey() {
+ return profileKey;
+ }
+
+ public boolean isAssociated() {
+ return profileKey != null;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
new file mode 100644
index 00000000000..b184b493e01
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
@@ -0,0 +1,376 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+import org.sonar.db.component.ComponentDto;
+
+@ServerSide
+public class QualityProfileDao implements Dao {
+
+ private final MyBatis mybatis;
+ private final System2 system;
+
+ public QualityProfileDao(MyBatis mybatis, System2 system) {
+ this.mybatis = mybatis;
+ this.system = system;
+ }
+
+ @CheckForNull
+ public QualityProfileDto getByKey(DbSession session, String key) {
+ return getMapper(session).selectByKey(key);
+ }
+
+ public QualityProfileDto getNonNullByKey(DbSession session, String key) {
+ QualityProfileDto dto = getByKey(session, key);
+ if (dto == null) {
+ throw new IllegalArgumentException("Quality profile not found: " + key);
+ }
+ return dto;
+ }
+
+ public List<QualityProfileDto> findAll(DbSession session) {
+ return getMapper(session).selectAll();
+ }
+
+ public void insert(DbSession session, QualityProfileDto profile, QualityProfileDto... otherProfiles) {
+ QualityProfileMapper mapper = getMapper(session);
+ doInsert(mapper, profile);
+ for (QualityProfileDto other : otherProfiles) {
+ doInsert(mapper, other);
+ }
+ }
+
+ private void doInsert(QualityProfileMapper mapper, QualityProfileDto profile) {
+ Preconditions.checkArgument(profile.getId() == null, "Quality profile is already persisted (got id %d)", profile.getId());
+ Date now = new Date(system.now());
+ profile.setCreatedAt(now);
+ profile.setUpdatedAt(now);
+ mapper.insert(profile);
+ }
+
+ /**
+ * @deprecated use {@link #insert(DbSession, QualityProfileDto, QualityProfileDto...)}
+ */
+ @Deprecated
+ public void insert(QualityProfileDto dto) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ insert(session, dto);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void update(DbSession session, QualityProfileDto profile, QualityProfileDto... otherProfiles) {
+ QualityProfileMapper mapper = getMapper(session);
+ doUpdate(mapper, profile);
+ for (QualityProfileDto otherProfile : otherProfiles) {
+ doUpdate(mapper, otherProfile);
+ }
+ }
+
+ private void doUpdate(QualityProfileMapper mapper, QualityProfileDto profile) {
+ Preconditions.checkArgument(profile.getId() != null, "Quality profile is not persisted");
+ profile.setUpdatedAt(new Date(system.now()));
+ mapper.update(profile);
+ }
+
+ /**
+ * @deprecated use {@link #update(DbSession, QualityProfileDto, QualityProfileDto...)}
+ */
+ @Deprecated
+ public void update(QualityProfileDto dto) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ update(session, dto);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void delete(DbSession session, QualityProfileDto profile, QualityProfileDto... otherProfiles) {
+ QualityProfileMapper mapper = getMapper(session);
+ doDelete(mapper, profile);
+ for (QualityProfileDto otherProfile : otherProfiles) {
+ doDelete(mapper, otherProfile);
+ }
+ }
+
+ private void doDelete(QualityProfileMapper mapper, QualityProfileDto profile) {
+ Preconditions.checkNotNull(profile.getId(), "Quality profile is not persisted");
+ mapper.delete(profile.getId());
+ }
+
+ /**
+ * @deprecated use {@link #delete(DbSession, QualityProfileDto, QualityProfileDto...)}
+ */
+ @Deprecated
+ public void delete(int id, DbSession session) {
+ getMapper(session).delete(id);
+ }
+
+ /**
+ * @deprecated use {@link #delete(DbSession, QualityProfileDto, QualityProfileDto...)}
+ */
+ @Deprecated
+ public void delete(int id) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ delete(id, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * @deprecated Replaced by
+ * {@link #findAll(DbSession)}
+ */
+ @Deprecated
+ public List<QualityProfileDto> findAll() {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectAll();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public QualityProfileDto getDefaultProfile(String language, DbSession session) {
+ return getMapper(session).selectDefaultProfile(language);
+ }
+
+ @CheckForNull
+ public QualityProfileDto getDefaultProfile(String language) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getDefaultProfile(language, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public QualityProfileDto getByProjectAndLanguage(long projectId, String language) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectByProjectIdAndLanguage(projectId, language);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public QualityProfileDto getByProjectAndLanguage(String projectKey, String language, DbSession session) {
+ return getMapper(session).selectByProjectAndLanguage(projectKey, language);
+ }
+
+ public List<QualityProfileDto> findByLanguage(String language) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectByLanguage(language);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * @deprecated Replaced by
+ * {@link #getByKey(DbSession, String)}
+ */
+ @Deprecated
+ @CheckForNull
+ public QualityProfileDto getById(int id, DbSession session) {
+ return getMapper(session).selectById(id);
+ }
+
+ /**
+ * @deprecated Replaced by
+ * {@link #getByKey(DbSession, String)}
+ */
+ @Deprecated
+ @CheckForNull
+ public QualityProfileDto getById(int id) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getById(id, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public QualityProfileDto getParent(String childKey, DbSession session) {
+ return getMapper(session).selectParent(childKey);
+ }
+
+ @CheckForNull
+ public QualityProfileDto getParent(String childKey) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getParent(childKey, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public QualityProfileDto getParentById(int childId, DbSession session) {
+ return getMapper(session).selectParentById(childId);
+ }
+
+ @CheckForNull
+ public QualityProfileDto getParentById(int childId) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getParentById(childId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<QualityProfileDto> findChildren(DbSession session, String key) {
+ return getMapper(session).selectChildren(key);
+ }
+
+ /**
+ * All descendants, in the top-down order.
+ */
+ public List<QualityProfileDto> findDescendants(DbSession session, String key) {
+ List<QualityProfileDto> descendants = Lists.newArrayList();
+ for (QualityProfileDto child : findChildren(session, key)) {
+ descendants.add(child);
+ descendants.addAll(findDescendants(session, child.getKey()));
+ }
+ return descendants;
+ }
+
+ @CheckForNull
+ public QualityProfileDto getByNameAndLanguage(String name, String language, DbSession session) {
+ return getMapper(session).selectByNameAndLanguage(name, language);
+ }
+
+ /**
+ * @deprecated Replaced by
+ * {@link #getByNameAndLanguage(String, String, DbSession)}
+ */
+ @Deprecated
+ public QualityProfileDto getByNameAndLanguage(String name, String language) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getByNameAndLanguage(name, language, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ComponentDto> selectProjects(String profileName, String language) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return selectProjects(profileName, language, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<ComponentDto> selectProjects(String profileName, String language, DbSession session) {
+ return getMapper(session).selectProjects(profileName, language);
+ }
+
+ public int countProjects(String profileName, String language) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).countProjects(profileName, language);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Map<String, Long> countProjectsByProfileKey() {
+ DbSession session = mybatis.openSession(false);
+ try {
+ Map<String, Long> countByKey = Maps.newHashMap();
+ for (QualityProfileProjectCount count : getMapper(session).countProjectsByProfile()) {
+ countByKey.put(count.getProfileKey(), count.getProjectCount());
+ }
+ return countByKey;
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insertProjectProfileAssociation(String projectUuid, String profileKey, DbSession session) {
+ getMapper(session).insertProjectProfileAssociation(projectUuid, profileKey);
+ }
+
+ public void deleteProjectProfileAssociation(String projectUuid, String profileKey, DbSession session) {
+ getMapper(session).deleteProjectProfileAssociation(projectUuid, profileKey);
+ }
+
+ public void updateProjectProfileAssociation(String projectUuid, String profileKey, DbSession session) {
+ getMapper(session).updateProjectProfileAssociation(projectUuid, profileKey);
+ }
+
+ public void deleteAllProjectProfileAssociation(String profileKey, DbSession session) {
+ getMapper(session).deleteAllProjectProfileAssociation(profileKey);
+ }
+
+ public List<ProjectQprofileAssociationDto> selectSelectedProjects(String profileKey, @Nullable String query, DbSession session) {
+ String nameQuery = sqlQueryString(query);
+ return getMapper(session).selectSelectedProjects(profileKey, nameQuery);
+ }
+
+ public List<ProjectQprofileAssociationDto> selectDeselectedProjects(String profileKey, @Nullable String query, DbSession session) {
+ String nameQuery = sqlQueryString(query);
+ return getMapper(session).selectDeselectedProjects(profileKey, nameQuery);
+ }
+
+ public List<ProjectQprofileAssociationDto> selectProjectAssociations(String profileKey, @Nullable String query, DbSession session) {
+ String nameQuery = sqlQueryString(query);
+ return getMapper(session).selectProjectAssociations(profileKey, nameQuery);
+ }
+
+ private String sqlQueryString(String query) {
+ return query == null ? "%" : "%" + query.toUpperCase() + "%";
+ }
+
+ private QualityProfileMapper getMapper(DbSession session) {
+ return session.getMapper(QualityProfileMapper.class);
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDto.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDto.java
new file mode 100644
index 00000000000..2c36242e9af
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDto.java
@@ -0,0 +1,128 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.core.util.UtcDateUtils;
+import org.sonar.db.Dto;
+
+public class QualityProfileDto extends Dto<String> {
+
+ private Integer id;
+ private String kee;
+ private String name;
+ private String language;
+ private String parentKee;
+ private String rulesUpdatedAt;
+ private boolean isDefault;
+
+ /**
+ * @deprecated use {@link #createFor(String)}
+ */
+ @Deprecated
+ public QualityProfileDto() {
+
+ }
+
+ @Override
+ public String getKey() {
+ return kee;
+ }
+
+ public QualityProfileDto setKey(String s) {
+ return setKee(s);
+ }
+
+ public String getKee() {
+ return kee;
+ }
+
+ public QualityProfileDto setKee(String s) {
+ this.kee = s;
+ return this;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public QualityProfileDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public QualityProfileDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public QualityProfileDto setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ @CheckForNull
+ public String getParentKee() {
+ return parentKee;
+ }
+
+ public QualityProfileDto setParentKee(@Nullable String s) {
+ this.parentKee = s;
+ return this;
+ }
+
+ public String getRulesUpdatedAt() {
+ return rulesUpdatedAt;
+ }
+
+ public QualityProfileDto setRulesUpdatedAt(String s) {
+ this.rulesUpdatedAt = s;
+ return this;
+ }
+
+ public QualityProfileDto setRulesUpdatedAtAsDate(Date d) {
+ this.rulesUpdatedAt = UtcDateUtils.formatDateTime(d);
+ return this;
+ }
+
+ public boolean isDefault() {
+ return isDefault;
+ }
+
+ public QualityProfileDto setDefault(boolean isDefault) {
+ this.isDefault = isDefault;
+ return this;
+ }
+
+ public static QualityProfileDto createFor(String key) {
+ return new QualityProfileDto().setKee(key);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java
new file mode 100644
index 00000000000..ade9c166655
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java
@@ -0,0 +1,87 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+import org.sonar.db.component.ComponentDto;
+
+public interface QualityProfileMapper {
+
+ void insert(QualityProfileDto dto);
+
+ void update(QualityProfileDto dto);
+
+ void delete(int id);
+
+ List<QualityProfileDto> selectAll();
+
+ @CheckForNull
+ QualityProfileDto selectDefaultProfile(@Param("language") String language);
+
+ @CheckForNull
+ QualityProfileDto selectByNameAndLanguage(@Param("name") String name, @Param("language") String language);
+
+ @CheckForNull
+ QualityProfileDto selectById(@Param("id") Integer id);
+
+ @CheckForNull
+ QualityProfileDto selectByKey(String key);
+
+ List<QualityProfileDto> selectByLanguage(String language);
+
+ // INHERITANCE
+
+ @CheckForNull
+ QualityProfileDto selectParent(String childKey);
+
+ @CheckForNull
+ QualityProfileDto selectParentById(int childId);
+
+ List<QualityProfileDto> selectChildren(String key);
+
+ // PROJECTS
+
+ List<ComponentDto> selectProjects(@Param("profileName") String profileName, @Param("language") String language);
+
+ int countProjects(@Param("profileName") String profileName, @Param("language") String language);
+
+ List<QualityProfileProjectCount> countProjectsByProfile();
+
+ QualityProfileDto selectByProjectIdAndLanguage(@Param("projectId") Long projectId, @Param("language") String language);
+
+ QualityProfileDto selectByProjectAndLanguage(@Param("projectKey") String projectKey, @Param("language") String language);
+
+ void insertProjectProfileAssociation(@Param("projectUuid") String projectUuid, @Param("profileKey") String profileKey);
+
+ void updateProjectProfileAssociation(@Param("projectUuid") String projectUuid, @Param("profileKey") String profileKey);
+
+ void deleteProjectProfileAssociation(@Param("projectUuid") String projectUuid, @Param("profileKey") String profileKey);
+
+ void deleteAllProjectProfileAssociation(@Param("profileKey") String profileKey);
+
+ List<ProjectQprofileAssociationDto> selectSelectedProjects(@Param("profileKey") String profileKey, @Param("nameQuery") String nameQuery);
+
+ List<ProjectQprofileAssociationDto> selectDeselectedProjects(@Param("profileKey") String profileKey, @Param("nameQuery") String nameQuery);
+
+ List<ProjectQprofileAssociationDto> selectProjectAssociations(@Param("profileKey") String profileKey, @Param("nameQuery") String nameQuery);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileProjectCount.java b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileProjectCount.java
new file mode 100644
index 00000000000..287d7e93b36
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileProjectCount.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+public class QualityProfileProjectCount {
+
+ private String profileKey;
+ private Long projectCount;
+
+ public String getProfileKey() {
+ return profileKey;
+ }
+
+ public void setProfileKey(String profileKey) {
+ this.profileKey = profileKey;
+ }
+
+ public Long getProjectCount() {
+ return projectCount;
+ }
+
+ public void setProjectCount(Long projectCount) {
+ this.projectCount = projectCount;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java b/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java
new file mode 100644
index 00000000000..25236bc2bcb
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/rule/RuleDao.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.rule;
+
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+/**
+ * @deprecated in 4.4 moved to org.sonar.server.rule.db.RuleDao.
+ */
+@Deprecated
+public class RuleDao {
+
+ private MyBatis mybatis;
+
+ public RuleDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public List<RuleDto> selectEnablesAndNonManual() {
+ SqlSession session = mybatis.openSession();
+ try {
+ return selectEnablesAndNonManual(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<RuleDto> selectEnablesAndNonManual(SqlSession session) {
+ return getMapper(session).selectEnablesAndNonManual();
+ }
+
+ public List<RuleParamDto> selectParameters() {
+ SqlSession session = mybatis.openSession();
+ try {
+ return selectParameters(session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<RuleParamDto> selectParameters(SqlSession session) {
+ return getMapper(session).selectAllParams();
+ }
+
+ private RuleMapper getMapper(SqlSession session) {
+ return session.getMapper(RuleMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/rule/RuleDto.java b/sonar-db/src/main/java/org/sonar/db/rule/RuleDto.java
new file mode 100644
index 00000000000..d3188ec87db
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/rule/RuleDto.java
@@ -0,0 +1,403 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.rule;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.core.rule.SeverityUtil;
+import org.sonar.db.Dto;
+
+public class RuleDto extends Dto<RuleKey> {
+
+ public static final Integer DISABLED_CHARACTERISTIC_ID = -1;
+
+ public enum Format {
+ HTML, MARKDOWN
+ }
+
+ private Integer id;
+ private String repositoryKey;
+ private String ruleKey;
+ private String description;
+ private Format descriptionFormat;
+ private RuleStatus status;
+ private String name;
+ private String configKey;
+ private Integer severity;
+ private boolean isTemplate;
+ private String language;
+ private Integer templateId;
+ private String noteData;
+ private String noteUserLogin;
+ private Date noteCreatedAt;
+ private Date noteUpdatedAt;
+ private Integer subCharacteristicId;
+ private Integer defaultSubCharacteristicId;
+ private String remediationFunction;
+ private String defaultRemediationFunction;
+ private String remediationCoefficient;
+ private String defaultRemediationCoefficient;
+ private String remediationOffset;
+ private String defaultRemediationOffset;
+ private String effortToFixDescription;
+ private String tags;
+ private String systemTags;
+
+ private RuleKey key;
+
+ @Override
+ public RuleKey getKey() {
+ if (key == null) {
+ key = RuleKey.of(getRepositoryKey(), getRuleKey());
+ }
+ return key;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public RuleDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getRepositoryKey() {
+ return repositoryKey;
+ }
+
+ public RuleDto setRepositoryKey(String repositoryKey) {
+ this.repositoryKey = repositoryKey;
+ return this;
+ }
+
+ public String getRuleKey() {
+ return ruleKey;
+ }
+
+ public RuleDto setRuleKey(String ruleKey) {
+ this.ruleKey = ruleKey;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public RuleDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Format getDescriptionFormat() {
+ return descriptionFormat;
+ }
+
+ public RuleDto setDescriptionFormat(Format descriptionFormat) {
+ this.descriptionFormat = descriptionFormat;
+ return this;
+ }
+
+ public RuleStatus getStatus() {
+ return status;
+ }
+
+ public RuleDto setStatus(@Nullable RuleStatus s) {
+ this.status = s;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public RuleDto setName(@Nullable String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getConfigKey() {
+ return configKey;
+ }
+
+ public RuleDto setConfigKey(@Nullable String configKey) {
+ this.configKey = configKey;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getSeverity() {
+ return severity;
+ }
+
+ @CheckForNull
+ public String getSeverityString() {
+ return severity != null ? SeverityUtil.getSeverityFromOrdinal(severity) : null;
+ }
+
+ public RuleDto setSeverity(@Nullable String severity) {
+ return this.setSeverity(severity != null ? SeverityUtil.getOrdinalFromSeverity(severity) : null);
+ }
+
+ public RuleDto setSeverity(@Nullable Integer severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public boolean isTemplate() {
+ return isTemplate;
+ }
+
+ public RuleDto setIsTemplate(boolean isTemplate) {
+ this.isTemplate = isTemplate;
+ return this;
+ }
+
+ @CheckForNull
+ public String getLanguage() {
+ return language;
+ }
+
+ public RuleDto setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getTemplateId() {
+ return templateId;
+ }
+
+ public RuleDto setTemplateId(@Nullable Integer templateId) {
+ this.templateId = templateId;
+ return this;
+ }
+
+ public String getNoteData() {
+ return noteData;
+ }
+
+ public RuleDto setNoteData(String noteData) {
+ this.noteData = noteData;
+ return this;
+ }
+
+ public String getNoteUserLogin() {
+ return noteUserLogin;
+ }
+
+ public RuleDto setNoteUserLogin(String noteUserLogin) {
+ this.noteUserLogin = noteUserLogin;
+ return this;
+ }
+
+ public Date getNoteCreatedAt() {
+ return noteCreatedAt;
+ }
+
+ public RuleDto setNoteCreatedAt(Date noteCreatedAt) {
+ this.noteCreatedAt = noteCreatedAt;
+ return this;
+ }
+
+ public Date getNoteUpdatedAt() {
+ return noteUpdatedAt;
+ }
+
+ public RuleDto setNoteUpdatedAt(Date noteUpdatedAt) {
+ this.noteUpdatedAt = noteUpdatedAt;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getSubCharacteristicId() {
+ return subCharacteristicId;
+ }
+
+ public RuleDto setSubCharacteristicId(@Nullable Integer subCharacteristicId) {
+ this.subCharacteristicId = subCharacteristicId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getDefaultSubCharacteristicId() {
+ return defaultSubCharacteristicId;
+ }
+
+ public RuleDto setDefaultSubCharacteristicId(@Nullable Integer defaultSubCharacteristicId) {
+ this.defaultSubCharacteristicId = defaultSubCharacteristicId;
+ return this;
+ }
+
+ @CheckForNull
+ public String getRemediationFunction() {
+ return remediationFunction;
+ }
+
+ public RuleDto setRemediationFunction(@Nullable String remediationFunction) {
+ this.remediationFunction = remediationFunction;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDefaultRemediationFunction() {
+ return defaultRemediationFunction;
+ }
+
+ public RuleDto setDefaultRemediationFunction(@Nullable String defaultRemediationFunction) {
+ this.defaultRemediationFunction = defaultRemediationFunction;
+ return this;
+ }
+
+ @CheckForNull
+ public String getRemediationCoefficient() {
+ return remediationCoefficient;
+ }
+
+ public RuleDto setRemediationCoefficient(@Nullable String remediationCoefficient) {
+ this.remediationCoefficient = remediationCoefficient;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDefaultRemediationCoefficient() {
+ return defaultRemediationCoefficient;
+ }
+
+ public RuleDto setDefaultRemediationCoefficient(@Nullable String defaultRemediationCoefficient) {
+ this.defaultRemediationCoefficient = defaultRemediationCoefficient;
+ return this;
+ }
+
+ @CheckForNull
+ public String getRemediationOffset() {
+ return remediationOffset;
+ }
+
+ public RuleDto setRemediationOffset(@Nullable String remediationOffset) {
+ this.remediationOffset = remediationOffset;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDefaultRemediationOffset() {
+ return defaultRemediationOffset;
+ }
+
+ public RuleDto setDefaultRemediationOffset(@Nullable String defaultRemediationOffset) {
+ this.defaultRemediationOffset = defaultRemediationOffset;
+ return this;
+ }
+
+ @CheckForNull
+ public String getEffortToFixDescription() {
+ return effortToFixDescription;
+ }
+
+ public RuleDto setEffortToFixDescription(@Nullable String s) {
+ this.effortToFixDescription = s;
+ return this;
+ }
+
+ public Set<String> getTags() {
+ return tags == null ?
+ new HashSet<String>() :
+ new TreeSet<>(Arrays.asList(StringUtils.split(tags, ',')));
+ }
+
+ public Set<String> getSystemTags() {
+ return systemTags == null ?
+ new HashSet<String>() :
+ new TreeSet<>(Arrays.asList(StringUtils.split(systemTags, ',')));
+ }
+
+ private String getTagsField() {
+ return tags;
+ }
+
+ private String getSystemTagsField() {
+ return systemTags;
+ }
+
+ private void setTagsField(String s) {
+ tags = s;
+ }
+
+ private void setSystemTagsField(String s) {
+ systemTags = s;
+ }
+
+ public RuleDto setTags(Set<String> tags) {
+ this.tags = tags.isEmpty() ? null : StringUtils.join(tags, ',');
+ return this;
+ }
+
+ public RuleDto setSystemTags(Set<String> tags) {
+ this.systemTags = tags.isEmpty() ? null : StringUtils.join(tags, ',');
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RuleDto)) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ RuleDto other = (RuleDto) obj;
+ return new EqualsBuilder()
+ .append(repositoryKey, other.getRepositoryKey())
+ .append(ruleKey, other.getRuleKey())
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(repositoryKey)
+ .append(ruleKey)
+ .toHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
+ }
+
+ public static RuleDto createFor(RuleKey key) {
+ return new RuleDto()
+ .setRepositoryKey(key.repository())
+ .setRuleKey(key.rule());
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java b/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java
new file mode 100644
index 00000000000..5074160a11c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/rule/RuleMapper.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.rule;
+
+import java.sql.Timestamp;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+import org.sonar.api.rule.RuleKey;
+
+public interface RuleMapper {
+
+ List<RuleDto> selectAll();
+
+ List<RuleDto> selectEnablesAndNonManual();
+
+ List<RuleDto> selectNonManual();
+
+ List<RuleDto> selectBySubCharacteristicId(int characteristicId);
+
+ RuleDto selectById(Integer id);
+
+ RuleDto selectByKey(RuleKey ruleKey);
+
+ RuleDto selectByName(String name);
+
+ void update(RuleDto rule);
+
+ void batchInsert(RuleDto rule);
+
+ void insert(RuleDto rule);
+
+ List<RuleParamDto> selectAllParams();
+
+ List<RuleParamDto> selectParamsByRuleIds(@Param("ruleIds") List<Integer> ruleIds);
+
+ List<RuleParamDto> selectParamsByRuleKey(RuleKey ruleKey);
+
+ RuleParamDto selectParamByRuleAndKey(@Param("ruleId") Integer ruleId, @Param("key") String key);
+
+ void insertParameter(RuleParamDto param);
+
+ void updateParameter(RuleParamDto param);
+
+ void deleteParameter(Integer paramId);
+
+ List<RuleDto> selectAfterDate(@Nullable @Param("date") Timestamp timestamp);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/rule/RuleParamDto.java b/sonar-db/src/main/java/org/sonar/db/rule/RuleParamDto.java
new file mode 100644
index 00000000000..2df5c541a93
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/rule/RuleParamDto.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.rule;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+public class RuleParamDto {
+
+ private Integer id;
+ private Integer ruleId;
+ private String name;
+ private String type;
+ private String defaultValue;
+ private String description;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public RuleParamDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ public RuleParamDto setRuleId(Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public RuleParamDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public RuleParamDto setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public RuleParamDto setDefaultValue(@Nullable String defaultValue) {
+ this.defaultValue = defaultValue;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public RuleParamDto setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
+ }
+
+ public static RuleParamDto createFor(RuleDto rule) {
+ // Should eventually switch to RuleKey (RuleKey is available before insert)
+ return new RuleParamDto().setRuleId(rule.getId());
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDao.java b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDao.java
new file mode 100644
index 00000000000..c34a9b89542
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDao.java
@@ -0,0 +1,150 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import com.google.common.base.Strings;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.utils.Semaphores;
+import org.sonar.api.utils.System2;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.api.utils.DateUtils.longToDate;
+
+/**
+ * @since 3.4
+ */
+public class SemaphoreDao {
+
+ private static final String SEMAPHORE_NAME_MUST_NOT_BE_EMPTY = "Semaphore name must not be empty";
+ private final MyBatis mybatis;
+ private final System2 system;
+
+ public SemaphoreDao(MyBatis mybatis, System2 system) {
+ this.mybatis = mybatis;
+ this.system = system;
+ }
+
+ public Semaphores.Semaphore acquire(String name, int maxAgeInSeconds) {
+ checkArgument(!Strings.isNullOrEmpty(name), SEMAPHORE_NAME_MUST_NOT_BE_EMPTY);
+ checkArgument(maxAgeInSeconds >= 0, "Semaphore max age must be positive: " + maxAgeInSeconds);
+
+ try (SqlSession session = mybatis.openSession(false)) {
+ SemaphoreDto semaphore = tryToInsert(name, system.now(), session);
+ boolean isAcquired;
+ if (semaphore == null) {
+ semaphore = selectSemaphore(name, session);
+ isAcquired = acquireIfOutdated(name, maxAgeInSeconds, session);
+ } else {
+ isAcquired = true;
+ }
+ return createLock(semaphore, isAcquired);
+ }
+ }
+
+ public Semaphores.Semaphore acquire(String name) {
+ checkArgument(!Strings.isNullOrEmpty(name), SEMAPHORE_NAME_MUST_NOT_BE_EMPTY);
+
+ try (SqlSession session = mybatis.openSession(false)) {
+ SemaphoreDto semaphore = tryToInsert(name, system.now(), session);
+ if (semaphore == null) {
+ semaphore = selectSemaphore(name, session);
+ return createLock(semaphore, false);
+ } else {
+ return createLock(semaphore, true);
+ }
+ }
+ }
+
+ public void update(Semaphores.Semaphore semaphore) {
+ checkArgument(semaphore != null, "Semaphore must not be null");
+
+ try (SqlSession session = mybatis.openSession(false)) {
+ mapper(session).update(semaphore.getName(), system.now());
+ session.commit();
+ }
+ }
+
+ public void release(String name) {
+ checkArgument(!Strings.isNullOrEmpty(name), SEMAPHORE_NAME_MUST_NOT_BE_EMPTY);
+ try (SqlSession session = mybatis.openSession(false)) {
+ mapper(session).release(name);
+ session.commit();
+ }
+ }
+
+ private boolean acquireIfOutdated(String name, int maxAgeInSeconds, SqlSession session) {
+ long now = system.now();
+ long updatedBefore = now - (long) maxAgeInSeconds * 1000;
+
+ boolean ok = mapper(session).acquire(name, updatedBefore, now) == 1;
+ session.commit();
+ return ok;
+ }
+
+ /**
+ * Insert the semaphore and commit. Rollback and return null if the semaphore already exists in db (whatever
+ * the lock date)
+ */
+ @CheckForNull
+ private SemaphoreDto tryToInsert(String name, long lockedNow, SqlSession session) {
+ try {
+ long now = system.now();
+ SemaphoreDto semaphore = new SemaphoreDto()
+ .setName(name)
+ .setCreatedAt(now)
+ .setUpdatedAt(now)
+ .setLockedAt(lockedNow);
+ mapper(session).initialize(semaphore);
+ session.commit();
+ return semaphore;
+ } catch (Exception e) {
+ // probably because the semaphore already exists in db
+ session.rollback();
+ return null;
+ }
+ }
+
+ private Semaphores.Semaphore createLock(SemaphoreDto dto, boolean acquired) {
+ Semaphores.Semaphore semaphore = new Semaphores.Semaphore()
+ .setName(dto.getName())
+ .setLocked(acquired)
+ .setLockedAt(longToDate(dto.getLockedAt()))
+ .setCreatedAt(longToDate(dto.getCreatedAt()))
+ .setUpdatedAt(longToDate(dto.getUpdatedAt()));
+ if (!acquired) {
+ semaphore.setDurationSinceLocked(lockedSince(dto));
+ }
+ return semaphore;
+ }
+
+ private long lockedSince(SemaphoreDto semaphore) {
+ return system.now() - semaphore.getLockedAt();
+ }
+
+ protected SemaphoreDto selectSemaphore(String name, SqlSession session) {
+ return mapper(session).selectSemaphore(name);
+ }
+
+ private SemaphoreMapper mapper(SqlSession session) {
+ return session.getMapper(SemaphoreMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDto.java b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDto.java
new file mode 100644
index 00000000000..a26e6a35037
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreDto.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+
+/**
+ * @since 3.4
+ */
+public class SemaphoreDto {
+ private Long id;
+ private String name;
+ private String checksum;
+ private Long lockedAt;
+ private Long createdAt;
+ private Long updatedAt;
+
+ public String getName() {
+ return name;
+ }
+
+ public SemaphoreDto setName(String s) {
+ this.name = s;
+ this.checksum = DigestUtils.md5Hex(s);
+ return this;
+ }
+
+ public Long getLockedAt() {
+ return lockedAt;
+ }
+
+ public SemaphoreDto setLockedAt(Long d) {
+ this.lockedAt = d;
+ return this;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public SemaphoreDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ public SemaphoreDto setCreatedAt(Long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public SemaphoreDto setUpdatedAt(Long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreMapper.java b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreMapper.java
new file mode 100644
index 00000000000..fc517e9f42d
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreMapper.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import org.apache.ibatis.annotations.Param;
+
+public interface SemaphoreMapper {
+
+ int initialize(SemaphoreDto semaphore);
+
+ int acquire(@Param("name") String name, @Param("updatedBefore") Long updatedBefore, @Param("now") Long now);
+
+ void release(String name);
+
+ SemaphoreDto selectSemaphore(@Param("name") String name);
+
+ void update(@Param("name") String name, @Param("now") Long now);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreUpdater.java b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreUpdater.java
new file mode 100644
index 00000000000..767539d385c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoreUpdater.java
@@ -0,0 +1,80 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.Semaphores;
+
+/**
+ * @since 3.5
+ */
+@ServerSide
+public class SemaphoreUpdater {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SemaphoreUpdater.class);
+
+ private SemaphoreDao dao;
+
+ private Map<String, ScheduledExecutorService> handlers = Maps.newHashMap();
+
+ public SemaphoreUpdater(SemaphoreDao dao) {
+ this.dao = dao;
+ }
+
+ public void scheduleForUpdate(final Semaphores.Semaphore semaphore, int updatePeriodInSeconds) {
+ ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+ Runnable updater = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ LOG.debug("Updating semaphore " + semaphore.getName());
+ dao.update(semaphore);
+ } catch (Exception e) {
+ LOG.error("Unable to update semaphore", e);
+ }
+
+ }
+ };
+ scheduler.scheduleWithFixedDelay(updater, updatePeriodInSeconds, updatePeriodInSeconds, TimeUnit.SECONDS);
+ handlers.put(semaphore.getName(), scheduler);
+ }
+
+ public void stopUpdate(final String name) {
+ if (handlers.containsKey(name)) {
+ handlers.get(name).shutdown();
+ try {
+ if (!handlers.get(name).awaitTermination(1, TimeUnit.SECONDS)) {
+ LOG.error("Unable to cancel semaphore updater in 1 second");
+ }
+ } catch (InterruptedException e) {
+ LOG.error("Unable to cancel semaphore updater", e);
+ } finally {
+ handlers.remove(name);
+ }
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java
new file mode 100644
index 00000000000..63958124965
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java
@@ -0,0 +1,54 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import org.sonar.api.utils.Semaphores;
+
+/**
+ * @since 3.4
+ */
+public class SemaphoresImpl implements Semaphores {
+
+ private SemaphoreDao dao;
+ private SemaphoreUpdater updater;
+
+ public SemaphoresImpl(SemaphoreDao dao, SemaphoreUpdater updater) {
+ this.dao = dao;
+ this.updater = updater;
+ }
+
+ @Override
+ public Semaphore acquire(String name, int maxAgeInSeconds, int updatePeriodInSeconds) {
+ Semaphore semaphore = dao.acquire(name, maxAgeInSeconds);
+ updater.scheduleForUpdate(semaphore, updatePeriodInSeconds);
+ return semaphore;
+ }
+
+ @Override
+ public Semaphore acquire(String name) {
+ return dao.acquire(name);
+ }
+
+ @Override
+ public void release(String name) {
+ updater.stopUpdate(name);
+ dao.release(name);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/semaphore/package-info.java b/sonar-db/src/main/java/org/sonar/db/semaphore/package-info.java
new file mode 100644
index 00000000000..888655d88da
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/semaphore/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.semaphore;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/source/FileSourceDto.java b/sonar-db/src/main/java/org/sonar/db/source/FileSourceDto.java
new file mode 100644
index 00000000000..0ac7561084f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/source/FileSourceDto.java
@@ -0,0 +1,277 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.source;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import net.jpountz.lz4.LZ4BlockInputStream;
+import net.jpountz.lz4.LZ4BlockOutputStream;
+import org.apache.commons.io.IOUtils;
+import org.sonar.server.source.db.FileSourceDb;
+import org.sonar.server.source.db.FileSourceDb.Test;
+
+public class FileSourceDto {
+
+ private Long id;
+ private String projectUuid;
+ private String fileUuid;
+ private long createdAt;
+ private long updatedAt;
+ private String lineHashes;
+ private String srcHash;
+ private byte[] binaryData;
+ private String dataType;
+ private String dataHash;
+
+ public Long getId() {
+ return id;
+ }
+
+ public FileSourceDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public FileSourceDto setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ public String getFileUuid() {
+ return fileUuid;
+ }
+
+ public FileSourceDto setFileUuid(String fileUuid) {
+ this.fileUuid = fileUuid;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDataHash() {
+ return dataHash;
+ }
+
+ /**
+ * MD5 of column BINARY_DATA. Used to know to detect data changes and need for update.
+ */
+ public FileSourceDto setDataHash(String s) {
+ this.dataHash = s;
+ return this;
+ }
+
+ public static FileSourceDb.Data decodeSourceData(byte[] binaryData) {
+ // stream is always closed
+ return decodeSourceData(new ByteArrayInputStream(binaryData));
+ }
+
+ /**
+ * Decompress and deserialize content of column FILE_SOURCES.BINARY_DATA.
+ * The parameter "input" is always closed by this method.
+ */
+ public static FileSourceDb.Data decodeSourceData(InputStream binaryInput) {
+ LZ4BlockInputStream lz4Input = null;
+ try {
+ lz4Input = new LZ4BlockInputStream(binaryInput);
+ return FileSourceDb.Data.parseFrom(lz4Input);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to decompress and deserialize source data", e);
+ } finally {
+ IOUtils.closeQuietly(lz4Input);
+ }
+ }
+
+ /**
+ * Serialize and compress protobuf message {@link org.sonar.server.source.db.FileSourceDb.Data}
+ * in the column BINARY_DATA.
+ */
+ public static byte[] encodeSourceData(FileSourceDb.Data data) {
+ ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
+ LZ4BlockOutputStream compressedOutput = new LZ4BlockOutputStream(byteOutput);
+ try {
+ data.writeTo(compressedOutput);
+ compressedOutput.close();
+ return byteOutput.toByteArray();
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to serialize and compress source data", e);
+ } finally {
+ IOUtils.closeQuietly(compressedOutput);
+ }
+ }
+
+ public static List<Test> decodeTestData(byte[] binaryData) {
+ // stream is always closed
+ return decodeTestData(new ByteArrayInputStream(binaryData));
+ }
+
+ /**
+ * Decompress and deserialize content of column FILE_SOURCES.BINARY_DATA.
+ * The parameter "input" is always closed by this method.
+ */
+ public static List<Test> decodeTestData(InputStream binaryInput) {
+ LZ4BlockInputStream lz4Input = null;
+ List<Test> tests = new ArrayList<>();
+ try {
+ lz4Input = new LZ4BlockInputStream(binaryInput);
+
+ Test currentTest;
+ do {
+ currentTest = Test.parseDelimitedFrom(lz4Input);
+ if (currentTest != null) {
+ tests.add(currentTest);
+ }
+ } while (currentTest != null);
+ return tests;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to decompress and deserialize source data", e);
+ } finally {
+ IOUtils.closeQuietly(lz4Input);
+ }
+ }
+
+ /**
+ * Serialize and compress protobuf message {@link org.sonar.server.source.db.FileSourceDb.Data}
+ * in the column BINARY_DATA.
+ */
+ public static byte[] encodeTestData(List<Test> tests) {
+ ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
+ LZ4BlockOutputStream compressedOutput = new LZ4BlockOutputStream(byteOutput);
+ try {
+ for (Test test : tests) {
+ test.writeDelimitedTo(compressedOutput);
+ }
+ compressedOutput.close();
+ return byteOutput.toByteArray();
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to serialize and compress source tests", e);
+ } finally {
+ IOUtils.closeQuietly(compressedOutput);
+ }
+ }
+
+ /**
+ * Compressed value of serialized protobuf message {@link org.sonar.server.source.db.FileSourceDb.Data}
+ */
+ public byte[] getBinaryData() {
+ return binaryData;
+ }
+
+ /**
+ * Set compressed value of the protobuf message {@link org.sonar.server.source.db.FileSourceDb.Data}
+ */
+ public FileSourceDto setBinaryData(byte[] data) {
+ this.binaryData = data;
+ return this;
+ }
+
+ /**
+ * Compressed value of serialized protobuf message {@link org.sonar.server.source.db.FileSourceDb.Data}
+ */
+ public FileSourceDb.Data getSourceData() {
+ return decodeSourceData(binaryData);
+ }
+
+ public FileSourceDto setSourceData(FileSourceDb.Data data) {
+ this.dataType = Type.SOURCE;
+ this.binaryData = encodeSourceData(data);
+ return this;
+ }
+
+ /**
+ * Compressed value of serialized protobuf message {@link org.sonar.server.source.db.FileSourceDb.Data}
+ */
+ public List<Test> getTestData() {
+ return decodeTestData(binaryData);
+ }
+
+ public FileSourceDto setTestData(List<Test> data) {
+ this.dataType = Type.TEST;
+ this.binaryData = encodeTestData(data);
+ return this;
+ }
+
+ @CheckForNull
+ public String getLineHashes() {
+ return lineHashes;
+ }
+
+ public FileSourceDto setLineHashes(@Nullable String lineHashes) {
+ this.lineHashes = lineHashes;
+ return this;
+ }
+
+ @CheckForNull
+ public String getSrcHash() {
+ return srcHash;
+ }
+
+ /**
+ * Hash of file content. Value is computed by batch.
+ */
+ public FileSourceDto setSrcHash(@Nullable String srcHash) {
+ this.srcHash = srcHash;
+ return this;
+ }
+
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ public FileSourceDto setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public FileSourceDto setUpdatedAt(long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public String getDataType() {
+ return dataType;
+ }
+
+ public FileSourceDto setDataType(String dataType) {
+ this.dataType = dataType;
+ return this;
+ }
+
+ public static class Type {
+ public static final String SOURCE = "SOURCE";
+ public static final String TEST = "TEST";
+
+ private Type() {
+ // utility class
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/source/FileSourceMapper.java b/sonar-db/src/main/java/org/sonar/db/source/FileSourceMapper.java
new file mode 100644
index 00000000000..45b03b15b65
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/source/FileSourceMapper.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.source;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface FileSourceMapper {
+
+ List<FileSourceDto> selectHashesForProject(@Param("projectUuid") String projectUuid, @Param("dataType") String dataType);
+
+ @CheckForNull
+ FileSourceDto select(@Param("fileUuid") String fileUuid, @Param("dataType") String dataType);
+
+ void insert(FileSourceDto dto);
+
+ void update(FileSourceDto dto);
+
+ void updateDateWhenUpdatedDateIsZero(@Param("projectUuid") String projectUuid, @Param("date") Long updateDate);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/AuthorDao.java b/sonar-db/src/main/java/org/sonar/db/user/AuthorDao.java
new file mode 100644
index 00000000000..5837964ce82
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/AuthorDao.java
@@ -0,0 +1,122 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.DaoUtils;
+import org.sonar.db.MyBatis;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceDto;
+
+/**
+ * Be careful when updating this class because it's used by the Dev Cockpit plugin.
+ */
+public class AuthorDao implements Dao {
+
+ private final MyBatis mybatis;
+ private final ResourceDao resourceDao;
+
+ public AuthorDao(MyBatis mybatis, ResourceDao resourceDao) {
+ this.mybatis = mybatis;
+ this.resourceDao = resourceDao;
+ }
+
+ public AuthorDto selectByLogin(String login) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ AuthorMapper mapper = session.getMapper(AuthorMapper.class);
+ return mapper.selectByLogin(login);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public int countDeveloperLogins(long developerId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ AuthorMapper mapper = session.getMapper(AuthorMapper.class);
+ return mapper.countDeveloperLogins(developerId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insertAuthor(String login, long personId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ insertAuthor(login, personId, session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public void insertAuthorAndDeveloper(String login, ResourceDto resourceDto) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ // Hack in order to set the right module uuid path on DEVs
+ if (Strings.isNullOrEmpty(resourceDto.getModuleUuidPath())) {
+ resourceDto.setModuleUuidPath(ComponentDto.MODULE_UUID_PATH_SEP + resourceDto.getUuid() + ComponentDto.MODULE_UUID_PATH_SEP);
+ }
+ resourceDao.insertUsingExistingSession(resourceDto, session);
+ insertAuthor(login, resourceDto.getId(), session);
+ session.commit();
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private void insertAuthor(String login, long personId, SqlSession session) {
+ AuthorMapper authorMapper = session.getMapper(AuthorMapper.class);
+ Date now = new Date();
+ AuthorDto authorDto = new AuthorDto()
+ .setLogin(login)
+ .setPersonId(personId)
+ .setCreatedAt(now)
+ .setUpdatedAt(now);
+
+ authorMapper.insert(authorDto);
+ }
+
+ public List<String> selectScmAccountsByDeveloperUuids(Collection<String> developerUuid) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectScmAccountsByDeveloperUuids(session, developerUuid);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<String> selectScmAccountsByDeveloperUuids(final SqlSession session, Collection<String> developerUuids) {
+ return DaoUtils.executeLargeInputs(developerUuids, new Function<List<String>, List<String>>() {
+ @Override
+ public List<String> apply(List<String> partition) {
+ return session.getMapper(AuthorMapper.class).selectScmAccountsByDeveloperUuids(partition);
+ }
+ });
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/AuthorDto.java b/sonar-db/src/main/java/org/sonar/db/user/AuthorDto.java
new file mode 100644
index 00000000000..32275ab680e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/AuthorDto.java
@@ -0,0 +1,80 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.Date;
+
+/**
+ * @since 3.0
+ */
+public final class AuthorDto {
+
+ private Long id;
+ private Long personId;
+ private String login;
+ private Date createdAt;
+ private Date updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public AuthorDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getPersonId() {
+ return personId;
+ }
+
+ public AuthorDto setPersonId(Long personId) {
+ this.personId = personId;
+ return this;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public AuthorDto setLogin(String login) {
+ this.login = login;
+ return this;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;// NOSONAR May expose internal representation by returning reference to mutable object
+ }
+
+ public AuthorDto setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;// NOSONAR May expose internal representation by incorporating reference to mutable object
+ return this;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;// NOSONAR May expose internal representation by returning reference to mutable object
+ }
+
+ public AuthorDto setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;// NOSONAR May expose internal representation by incorporating reference to mutable object
+ return this;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/AuthorMapper.java b/sonar-db/src/main/java/org/sonar/db/user/AuthorMapper.java
new file mode 100644
index 00000000000..06f12adf1d5
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/AuthorMapper.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @since 3.0
+ */
+public interface AuthorMapper {
+
+ AuthorDto selectByLogin(String login);
+
+ void insert(AuthorDto authorDto);
+
+ int countDeveloperLogins(long developerId);
+
+ List<String> selectScmAccountsByDeveloperUuids(@Param("uuids") Collection<String> uuids);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/AuthorizationDao.java b/sonar-db/src/main/java/org/sonar/db/user/AuthorizationDao.java
new file mode 100644
index 00000000000..2a386c1d2b7
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/AuthorizationDao.java
@@ -0,0 +1,134 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.Dao;
+import org.sonar.db.DaoUtils;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Maps.newHashMap;
+
+@ServerSide
+public class AuthorizationDao implements Dao {
+
+ private static final String USER_ID_PARAM = "userId";
+ private final MyBatis mybatis;
+
+ public AuthorizationDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ public Collection<Long> keepAuthorizedProjectIds(final DbSession session, final Collection<Long> componentIds, @Nullable final Integer userId, final String role) {
+ if (componentIds.isEmpty()) {
+ return Collections.emptySet();
+ }
+ return DaoUtils.executeLargeInputs(componentIds, new Function<List<Long>, List<Long>>() {
+ @Override
+ public List<Long> apply(List<Long> partition) {
+ if (userId == null) {
+ return session.getMapper(AuthorizationMapper.class).keepAuthorizedProjectIdsForAnonymous(role, componentIds);
+ } else {
+ return session.getMapper(AuthorizationMapper.class).keepAuthorizedProjectIdsForUser(userId, role, componentIds);
+ }
+ }
+ });
+ }
+
+ /**
+ * Used by the Views Plugin
+ */
+ public boolean isAuthorizedComponentKey(String componentKey, @Nullable Integer userId, String role) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return keepAuthorizedComponentKeys(session, componentKey, userId, role).size() == 1;
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private List<String> keepAuthorizedComponentKeys(final DbSession session, final String componentKey, @Nullable final Integer userId, final String role) {
+ if (userId == null) {
+ return session.getMapper(AuthorizationMapper.class).keepAuthorizedComponentKeysForAnonymous(role, Sets.newHashSet(componentKey));
+ } else {
+ return session.getMapper(AuthorizationMapper.class).keepAuthorizedComponentKeysForUser(userId, role, Sets.newHashSet(componentKey));
+ }
+ }
+
+ public Collection<String> selectAuthorizedRootProjectsKeys(@Nullable Integer userId, String role) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectAuthorizedRootProjectsKeys(userId, role, session);
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Collection<String> selectAuthorizedRootProjectsUuids(@Nullable Integer userId, String role) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectAuthorizedRootProjectsUuids(userId, role, session);
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public Collection<String> selectAuthorizedRootProjectsKeys(@Nullable Integer userId, String role, SqlSession session) {
+ String sql;
+ Map<String, Object> params = newHashMap();
+ sql = "selectAuthorizedRootProjectsKeys";
+ params.put(USER_ID_PARAM, userId);
+ params.put("role", role);
+
+ return session.selectList(sql, params);
+ }
+
+ public Collection<String> selectAuthorizedRootProjectsUuids(@Nullable Integer userId, String role, SqlSession session) {
+ String sql;
+ Map<String, Object> params = newHashMap();
+ sql = "selectAuthorizedRootProjectsUuids";
+ params.put(USER_ID_PARAM, userId);
+ params.put("role", role);
+
+ return session.selectList(sql, params);
+ }
+
+ public List<String> selectGlobalPermissions(@Nullable String userLogin) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ Map<String, Object> params = newHashMap();
+ params.put("userLogin", userLogin);
+ return session.selectList("selectGlobalPermissions", params);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/AuthorizationMapper.java b/sonar-db/src/main/java/org/sonar/db/user/AuthorizationMapper.java
new file mode 100644
index 00000000000..ca2eadd15d2
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/AuthorizationMapper.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface AuthorizationMapper {
+
+ List<Long> keepAuthorizedProjectIdsForAnonymous(@Param("role") String role, @Param("componentIds") Collection<Long> componentIds);
+
+ List<Long> keepAuthorizedProjectIdsForUser(@Param("userId") Integer userId, @Param("role") String role, @Param("componentIds") Collection<Long> componentIds);
+
+ List<String> keepAuthorizedComponentKeysForAnonymous(@Param("role") String role, @Param("componentKeys") Collection<String> componentKeys);
+
+ List<String> keepAuthorizedComponentKeysForUser(@Param("userId") Integer userId, @Param("role") String role, @Param("componentKeys") Collection<String> componentKeys);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupDto.java b/sonar-db/src/main/java/org/sonar/db/user/GroupDto.java
new file mode 100644
index 00000000000..dd483344959
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupDto.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.db.Dto;
+
+public class GroupDto extends Dto<String> {
+
+ private Long id;
+ private String name;
+ private String description;
+
+ public Long getId() {
+ return id;
+ }
+
+ public GroupDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public GroupDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public GroupDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public String getKey() {
+ return name;
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupMapper.java b/sonar-db/src/main/java/org/sonar/db/user/GroupMapper.java
new file mode 100644
index 00000000000..d1cafb62fa9
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupMapper.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.session.RowBounds;
+
+public interface GroupMapper {
+
+ @CheckForNull
+ GroupDto selectByKey(String name);
+
+ @CheckForNull
+ GroupDto selectById(long groupId);
+
+ List<GroupDto> selectByUserLogin(String userLogin);
+
+ void insert(GroupDto groupDto);
+
+ void update(GroupDto item);
+
+ List<GroupDto> selectByQuery(String query, RowBounds rowBounds);
+
+ int countByQuery(String query);
+
+ void deleteById(long groupId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDao.java b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDao.java
new file mode 100644
index 00000000000..a62609259b8
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDao.java
@@ -0,0 +1,118 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.Dao;
+import org.sonar.db.DaoUtils;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class GroupMembershipDao implements Dao {
+
+ private final MyBatis mybatis;
+
+ public GroupMembershipDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ // TODO Remove this method and associated client code when the UI is migrated to Backbone
+ public List<GroupMembershipDto> selectGroups(GroupMembershipQuery query, Long userId, int offset, int limit) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return selectGroups(session, query, userId, offset, limit);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public List<GroupMembershipDto> selectGroups(SqlSession session, GroupMembershipQuery query, Long userId, int offset, int limit) {
+ Map<String, Object> params = ImmutableMap.of("query", query, "userId", userId);
+ return mapper(session).selectGroups(params, new RowBounds(offset, limit));
+ }
+
+ public int countGroups(SqlSession session, GroupMembershipQuery query, Long userId) {
+ Map<String, Object> params = ImmutableMap.of("query", query, "userId", userId);
+ return mapper(session).countGroups(params);
+ }
+
+ public List<UserMembershipDto> selectMembers(SqlSession session, UserMembershipQuery query, int offset, int limit) {
+ Map<String, Object> params = ImmutableMap.of("query", query, "groupId", query.groupId());
+ return mapper(session).selectMembers(params, new RowBounds(offset, limit));
+ }
+
+ public int countMembers(SqlSession session, UserMembershipQuery query) {
+ Map<String, Object> params = ImmutableMap.of("query", query, "groupId", query.groupId());
+ return mapper(session).countMembers(params);
+ }
+
+ public Map<String, Integer> countUsersByGroups(final DbSession session, Collection<Long> groupIds) {
+ final Map<String, Integer> result = Maps.newHashMap();
+ DaoUtils.executeLargeInputs(groupIds, new Function<List<Long>, List<GroupUserCount>>() {
+ @Override
+ public List<GroupUserCount> apply(@Nonnull List<Long> input) {
+ List<GroupUserCount> userCounts = mapper(session).countUsersByGroup(input);
+ for (GroupUserCount count : userCounts) {
+ result.put(count.groupName(), count.userCount());
+ }
+ return userCounts;
+ }
+ });
+
+ return result;
+ }
+
+ public Multimap<String, String> selectGroupsByLogins(final DbSession session, Collection<String> logins) {
+ final Multimap<String, String> result = ArrayListMultimap.create();
+ DaoUtils.executeLargeInputs(logins, new Function<List<String>, List<LoginGroup>>() {
+ @Override
+ public List<LoginGroup> apply(@Nonnull List<String> input) {
+ List<LoginGroup> groupMemberships = mapper(session).selectGroupsByLogins(input);
+ for (LoginGroup membership : groupMemberships) {
+ result.put(membership.login(), membership.groupName());
+ }
+ return groupMemberships;
+ }
+ });
+
+ return result;
+ }
+
+ @VisibleForTesting
+ List<GroupMembershipDto> selectGroups(GroupMembershipQuery query, Long userId) {
+ return selectGroups(query, userId, 0, Integer.MAX_VALUE);
+ }
+
+ private GroupMembershipMapper mapper(SqlSession session) {
+ return session.getMapper(GroupMembershipMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDto.java b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDto.java
new file mode 100644
index 00000000000..65e32e9b7e1
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipDto.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.core.user.GroupMembership;
+
+/**
+ * @since 4.1
+ */
+public class GroupMembershipDto {
+
+ private Long id;
+ private String name;
+ private String description;
+ private Long userId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public GroupMembershipDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public GroupMembershipDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ public GroupMembershipDto setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getUserId() {
+ return userId;
+ }
+
+ public GroupMembershipDto setUserId(@Nullable Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public GroupMembership toGroupMembership() {
+ return new GroupMembership()
+ .setId(id)
+ .setName(name)
+ .setDescription(description)
+ .setMember(userId != null);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipMapper.java b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipMapper.java
new file mode 100644
index 00000000000..37dda516a23
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipMapper.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.RowBounds;
+
+public interface GroupMembershipMapper {
+
+ List<GroupMembershipDto> selectGroups(GroupMembershipQuery query);
+
+ List<GroupMembershipDto> selectGroups(Map<String, Object> params, RowBounds rowBounds);
+
+ int countGroups(Map<String, Object> params);
+
+ List<UserMembershipDto> selectMembers(Map<String, Object> params, RowBounds rowBounds);
+
+ int countMembers(Map<String, Object> params);
+
+ List<GroupUserCount> countUsersByGroup(@Param("groupIds") List<Long> groupIds);
+
+ List<LoginGroup> selectGroupsByLogins(@Param("logins") List<String> logins);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipQuery.java b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipQuery.java
new file mode 100644
index 00000000000..3dadb5963bf
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupMembershipQuery.java
@@ -0,0 +1,164 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class GroupMembershipQuery {
+
+ public static final int DEFAULT_PAGE_INDEX = 1;
+ public static final int DEFAULT_PAGE_SIZE = 100;
+
+ public static final String ANY = "ANY";
+ public static final String IN = "IN";
+ public static final String OUT = "OUT";
+ public static final Set<String> AVAILABLE_MEMBERSHIP = ImmutableSet.of(ANY, IN, OUT);
+
+ private final String login;
+ private final String membership;
+
+ private final String groupSearch;
+
+ // for internal use in MyBatis
+ final String groupSearchSql;
+
+ // max results per page
+ private final int pageSize;
+
+ // index of selected page. Start with 1.
+ private final int pageIndex;
+
+ private GroupMembershipQuery(Builder builder) {
+ this.login = builder.login;
+ this.membership = builder.membership;
+ this.groupSearch = builder.groupSearch;
+ this.groupSearchSql = groupSearchToSql(groupSearch);
+
+ this.pageSize = builder.pageSize;
+ this.pageIndex = builder.pageIndex;
+ }
+
+ private static String groupSearchToSql(@Nullable String s) {
+ String sql = null;
+ if (s != null) {
+ sql = StringUtils.replace(StringUtils.upperCase(s), "%", "/%");
+ sql = StringUtils.replace(sql, "_", "/_");
+ sql = "%" + sql + "%";
+ }
+ return sql;
+ }
+
+ public String login() {
+ return login;
+ }
+
+ @CheckForNull
+ public String membership() {
+ return membership;
+ }
+
+ /**
+ * Search for groups containing a given string
+ */
+ @CheckForNull
+ public String groupSearch() {
+ return groupSearch;
+ }
+
+ public int pageSize() {
+ return pageSize;
+ }
+
+ public int pageIndex() {
+ return pageIndex;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String login;
+ private String membership;
+ private String groupSearch;
+
+ private Integer pageIndex = DEFAULT_PAGE_INDEX;
+ private Integer pageSize = DEFAULT_PAGE_SIZE;
+
+ private Builder() {
+ }
+
+ public Builder login(String login) {
+ this.login = login;
+ return this;
+ }
+
+ public Builder membership(@Nullable String membership) {
+ this.membership = membership;
+ return this;
+ }
+
+ public Builder groupSearch(@Nullable String s) {
+ this.groupSearch = StringUtils.defaultIfBlank(s, null);
+ return this;
+ }
+
+ public Builder pageSize(@Nullable Integer i) {
+ this.pageSize = i;
+ return this;
+ }
+
+ public Builder pageIndex(@Nullable Integer i) {
+ this.pageIndex = i;
+ return this;
+ }
+
+ private void initMembership() {
+ membership = firstNonNull(membership, ANY);
+ checkArgument(AVAILABLE_MEMBERSHIP.contains(membership),
+ "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIP);
+ }
+
+ private void initPageSize() {
+ pageSize = firstNonNull(pageSize, DEFAULT_PAGE_SIZE);
+ }
+
+ private void initPageIndex() {
+ pageIndex = firstNonNull(pageIndex, DEFAULT_PAGE_INDEX);
+ checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
+ }
+
+ public GroupMembershipQuery build() {
+ checkNotNull(login, "User login cant be null.");
+ initMembership();
+ initPageIndex();
+ initPageSize();
+ return new GroupMembershipQuery(this);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupRoleDto.java b/sonar-db/src/main/java/org/sonar/db/user/GroupRoleDto.java
new file mode 100644
index 00000000000..9c24a10dcdc
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupRoleDto.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import javax.annotation.Nullable;
+
+/**
+ * @since 3.2
+ */
+public class GroupRoleDto {
+ private Long id;
+ private Long groupId;
+ private Long resourceId;
+ private String role;
+
+ public Long getId() {
+ return id;
+ }
+
+ public GroupRoleDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getGroupId() {
+ return groupId;
+ }
+
+ /**
+ * Null when Anyone
+ */
+ public GroupRoleDto setGroupId(@Nullable Long groupId) {
+ this.groupId = groupId;
+ return this;
+ }
+
+ @Nullable
+ public Long getResourceId() {
+ return resourceId;
+ }
+
+ public GroupRoleDto setResourceId(@Nullable Long resourceId) {
+ this.resourceId = resourceId;
+ return this;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public GroupRoleDto setRole(String role) {
+ this.role = role;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/GroupUserCount.java b/sonar-db/src/main/java/org/sonar/db/user/GroupUserCount.java
new file mode 100644
index 00000000000..0c82650f0da
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/GroupUserCount.java
@@ -0,0 +1,34 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+public class GroupUserCount {
+
+ private String groupName;
+ private int userCount;
+
+ public String groupName() {
+ return groupName;
+ }
+
+ public int userCount() {
+ return userCount;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/LoginGroup.java b/sonar-db/src/main/java/org/sonar/db/user/LoginGroup.java
new file mode 100644
index 00000000000..1f2b080147e
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/LoginGroup.java
@@ -0,0 +1,34 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+public class LoginGroup {
+
+ private String login;
+ private String groupName;
+
+ public String login() {
+ return login;
+ }
+
+ public String groupName() {
+ return groupName;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/RoleDao.java b/sonar-db/src/main/java/org/sonar/db/user/RoleDao.java
new file mode 100644
index 00000000000..d1314b65900
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/RoleDao.java
@@ -0,0 +1,79 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class RoleDao implements Dao {
+
+ public List<String> selectUserPermissions(DbSession session, String userLogin, @Nullable Long resourceId) {
+ return session.getMapper(RoleMapper.class).selectUserPermissions(userLogin, resourceId);
+ }
+
+ public List<String> selectGroupPermissions(DbSession session, String groupName, @Nullable Long resourceId) {
+ return session.getMapper(RoleMapper.class).selectGroupPermissions(groupName, resourceId, DefaultGroups.isAnyone(groupName));
+ }
+
+ public void insertGroupRole(GroupRoleDto groupRole, SqlSession session) {
+ mapper(session).insertGroupRole(groupRole);
+ }
+
+ public void insertUserRole(UserRoleDto userRole, SqlSession session) {
+ mapper(session).insertUserRole(userRole);
+ }
+
+ public void deleteUserRole(UserRoleDto userRole, SqlSession session) {
+ mapper(session).deleteUserRole(userRole);
+ }
+
+ public void deleteGroupRole(GroupRoleDto groupRole, SqlSession session) {
+ mapper(session).deleteGroupRole(groupRole);
+ }
+
+ public void deleteGroupRolesByResourceId(Long resourceId, SqlSession session) {
+ mapper(session).deleteGroupRolesByResourceId(resourceId);
+ }
+
+ public void deleteUserRolesByResourceId(Long resourceId, SqlSession session) {
+ mapper(session).deleteUserRolesByResourceId(resourceId);
+ }
+
+ public int countResourceGroupRoles(DbSession session, Long resourceId) {
+ return mapper(session).countResourceGroupRoles(resourceId);
+ }
+
+ public int countResourceUserRoles(DbSession session, Long resourceId) {
+ return mapper(session).countResourceUserRoles(resourceId);
+ }
+
+ public void deleteGroupRolesByGroupId(DbSession session, long groupId) {
+ mapper(session).deleteGroupRolesByGroupId(groupId);
+ }
+
+ private static RoleMapper mapper(SqlSession session) {
+ return session.getMapper(RoleMapper.class);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/RoleMapper.java b/sonar-db/src/main/java/org/sonar/db/user/RoleMapper.java
new file mode 100644
index 00000000000..f26b59874bf
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/RoleMapper.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @since 3.2
+ */
+public interface RoleMapper {
+
+ /**
+ * @return permissions from a user
+ */
+ List<String> selectUserPermissions(@Param("userLogin") String userLogin, @Nullable @Param("resourceId") Long resourceId);
+
+ /**
+ * @return permissions from to a group
+ */
+ List<String> selectGroupPermissions(@Param("groupName") String groupName, @Nullable @Param("resourceId") Long resourceId, @Param("isAnyOneGroup") Boolean isAnyOneGroup);
+
+ void insertGroupRole(GroupRoleDto groupRole);
+
+ void insertUserRole(UserRoleDto userRole);
+
+ void deleteUserRole(UserRoleDto userRole);
+
+ void deleteGroupRole(GroupRoleDto groupRole);
+
+ void deleteGroupRolesByResourceId(Long resourceId);
+
+ void deleteUserRolesByResourceId(Long resourceId);
+
+ int countResourceGroupRoles(Long resourceId);
+
+ int countResourceUserRoles(Long resourceId);
+
+ void deleteGroupRolesByGroupId(long groupId);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserDao.java b/sonar-db/src/main/java/org/sonar/db/user/UserDao.java
new file mode 100644
index 00000000000..985da344a29
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserDao.java
@@ -0,0 +1,175 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.collect.Lists;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.user.UserQuery;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+public class UserDao implements Dao {
+
+ private final MyBatis mybatis;
+ private final System2 system2;
+
+ public UserDao(MyBatis mybatis, System2 system2) {
+ this.mybatis = mybatis;
+ this.system2 = system2;
+ }
+
+ public UserDto getUser(long userId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getUser(userId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public UserDto getUser(long userId, SqlSession session) {
+ return session.getMapper(UserMapper.class).selectUser(userId);
+ }
+
+ /**
+ * Search for user by login. Disabled users are ignored.
+ *
+ * @return the user, null if user not found
+ */
+ @CheckForNull
+ public UserDto selectActiveUserByLogin(String login) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return selectActiveUserByLogin(session, login);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
+ public UserDto selectActiveUserByLogin(DbSession session, String login) {
+ UserMapper mapper = session.getMapper(UserMapper.class);
+ return mapper.selectUserByLogin(login);
+ }
+
+ public List<UserDto> selectUsersByLogins(List<String> logins) {
+ List<UserDto> users = Lists.newArrayList();
+ DbSession session = mybatis.openSession(false);
+ try {
+ users.addAll(selectUsersByLogins(session, logins));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ return users;
+ }
+
+ public List<UserDto> selectUsersByLogins(DbSession session, List<String> logins) {
+ List<UserDto> users = Lists.newArrayList();
+ if (!logins.isEmpty()) {
+ UserMapper mapper = session.getMapper(UserMapper.class);
+ List<List<String>> partitions = Lists.partition(logins, 1000);
+ for (List<String> partition : partitions) {
+ users.addAll(mapper.selectUsersByLogins(partition));
+ }
+ }
+ return users;
+ }
+
+ public List<UserDto> selectUsers(UserQuery query) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ UserMapper mapper = session.getMapper(UserMapper.class);
+ return mapper.selectUsers(query);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public UserDto insert(SqlSession session, UserDto dto) {
+ session.getMapper(UserMapper.class).insert(dto);
+ return dto;
+ }
+
+ public UserDto update(SqlSession session, UserDto dto) {
+ session.getMapper(UserMapper.class).update(dto);
+ return dto;
+ }
+
+ /**
+ * Deactivate a user and drops all his preferences.
+ * @return false if the user does not exist, true if the existing user has been deactivated
+ */
+ public boolean deactivateUserByLogin(String login) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ UserMapper mapper = session.getMapper(UserMapper.class);
+ UserDto dto = mapper.selectUserByLogin(login);
+ if (dto == null) {
+ return false;
+ }
+
+ mapper.removeUserFromGroups(dto.getId());
+ mapper.deleteUserActiveDashboards(dto.getId());
+ mapper.deleteUnsharedUserDashboards(dto.getId());
+ mapper.deleteUnsharedUserIssueFilters(dto.getLogin());
+ mapper.deleteUserIssueFilterFavourites(dto.getLogin());
+ mapper.deleteUnsharedUserMeasureFilters(dto.getId());
+ mapper.deleteUserMeasureFilterFavourites(dto.getId());
+ mapper.deleteUserProperties(dto.getId());
+ mapper.deleteUserRoles(dto.getId());
+ mapper.deactivateUser(dto.getId(), system2.now());
+ session.commit();
+ return true;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ /**
+ * Search for group by name.
+ *
+ * @return the group, null if group not found
+ *
+ * TODO should be moved to GroupDao
+ */
+ @CheckForNull
+ public GroupDto selectGroupByName(String name, DbSession session) {
+ UserMapper mapper = session.getMapper(UserMapper.class);
+ return mapper.selectGroupByName(name);
+ }
+
+ /**
+ * TODO should be moved to GroupDao
+ */
+ @CheckForNull
+ public GroupDto selectGroupByName(String name) {
+ DbSession session = mybatis.openSession(false);
+ try {
+ return selectGroupByName(name, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserDto.java b/sonar-db/src/main/java/org/sonar/db/user/UserDto.java
new file mode 100644
index 00000000000..b0d9f79d95f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserDto.java
@@ -0,0 +1,175 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.core.user.DefaultUser;
+
+/**
+ * @since 3.2
+ */
+public class UserDto {
+ public static final char SCM_ACCOUNTS_SEPARATOR = '\n';
+
+ private Long id;
+ private String login;
+ private String name;
+ private String email;
+ private boolean active = true;
+ private String scmAccounts;
+ private String cryptedPassword;
+ private String salt;
+ private Long createdAt;
+ private Long updatedAt;
+
+ public Long getId() {
+ return id;
+ }
+
+ public UserDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public UserDto setLogin(String login) {
+ this.login = login;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public UserDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getEmail() {
+ return email;
+ }
+
+ public UserDto setEmail(@Nullable String email) {
+ this.email = email;
+ return this;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public UserDto setActive(boolean b) {
+ this.active = b;
+ return this;
+ }
+
+ @CheckForNull
+ public String getScmAccounts() {
+ return scmAccounts;
+ }
+
+ public List<String> getScmAccountsAsList() {
+ return decodeScmAccounts(scmAccounts);
+ }
+
+ /**
+ * List of SCM accounts separated by '|'
+ */
+ public UserDto setScmAccounts(@Nullable String s) {
+ this.scmAccounts = s;
+ return this;
+ }
+
+ public UserDto setScmAccounts(@Nullable List list) {
+ this.scmAccounts = encodeScmAccounts(list);
+ return this;
+ }
+
+ @CheckForNull
+ public static String encodeScmAccounts(@Nullable List<String> scmAccounts) {
+ if (scmAccounts != null && !scmAccounts.isEmpty()) {
+ return String.format("%s%s%s", SCM_ACCOUNTS_SEPARATOR, StringUtils.join(scmAccounts, SCM_ACCOUNTS_SEPARATOR), SCM_ACCOUNTS_SEPARATOR);
+ }
+ return null;
+ }
+
+ public static List<String> decodeScmAccounts(@Nullable String dbValue) {
+ if (dbValue == null) {
+ return new ArrayList<>();
+ } else {
+ return Lists.newArrayList(Splitter.on(SCM_ACCOUNTS_SEPARATOR).omitEmptyStrings().split(dbValue));
+ }
+ }
+
+ public String getCryptedPassword() {
+ return cryptedPassword;
+ }
+
+ public UserDto setCryptedPassword(String cryptedPassword) {
+ this.cryptedPassword = cryptedPassword;
+ return this;
+ }
+
+ public String getSalt() {
+ return salt;
+ }
+
+ public UserDto setSalt(String salt) {
+ this.salt = salt;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ public UserDto setCreatedAt(Long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public UserDto setUpdatedAt(Long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public DefaultUser toUser() {
+ return new DefaultUser()
+ .setLogin(login)
+ .setName(name)
+ .setEmail(email)
+ .setActive(active);
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserGroupDto.java b/sonar-db/src/main/java/org/sonar/db/user/UserGroupDto.java
new file mode 100644
index 00000000000..d26510f69ad
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserGroupDto.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+public class UserGroupDto {
+
+ private Long userId;
+ private Long groupId;
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public UserGroupDto setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public Long getGroupId() {
+ return groupId;
+ }
+
+ public UserGroupDto setGroupId(Long groupId) {
+ this.groupId = groupId;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserGroupMapper.java b/sonar-db/src/main/java/org/sonar/db/user/UserGroupMapper.java
new file mode 100644
index 00000000000..6c723dbf838
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserGroupMapper.java
@@ -0,0 +1,30 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+public interface UserGroupMapper {
+
+ void insert(UserGroupDto userGroupDto);
+
+ void delete(UserGroupDto dto);
+
+ void deleteMembersByGroup(long groupId);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserMapper.java b/sonar-db/src/main/java/org/sonar/db/user/UserMapper.java
new file mode 100644
index 00000000000..e387210942a
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserMapper.java
@@ -0,0 +1,79 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+import org.sonar.api.user.UserQuery;
+
+public interface UserMapper {
+
+ @CheckForNull
+ UserDto selectByLogin(String login);
+
+ /**
+ * Search for a user by SCM account, login or email.
+ * Can return multiple results if an email is used by many users (For instance, technical account can use the same email as a none technical account)
+ */
+ @CheckForNull
+ List<UserDto> selectNullableByScmAccountOrLoginOrEmail(@Param("scmAccount") String scmAccountOrLoginOrEmail, @Param("likeScmAccount") String likeScmAccount);
+
+ @CheckForNull
+ UserDto selectUser(long userId);
+
+ /**
+ * Select user by login. Note that disabled users are ignored.
+ */
+ @CheckForNull
+ UserDto selectUserByLogin(String login);
+
+ List<UserDto> selectUsersByLogins(@Param("logins") List<String> logins);
+
+ List<UserDto> selectUsers(UserQuery query);
+
+ @CheckForNull
+ GroupDto selectGroupByName(String name);
+
+ void insert(UserDto userDto);
+
+ void update(UserDto userDto);
+
+ void removeUserFromGroups(long userId);
+
+ void deleteUserActiveDashboards(long userId);
+
+ void deleteUnsharedUserDashboards(long userId);
+
+ void deleteUnsharedUserIssueFilters(String login);
+
+ void deleteUserIssueFilterFavourites(String login);
+
+ void deleteUnsharedUserMeasureFilters(long userId);
+
+ void deleteUserMeasureFilterFavourites(long userId);
+
+ void deleteUserProperties(long userId);
+
+ void deleteUserRoles(long userId);
+
+ void deactivateUser(@Param("id") long userId, @Param("now") long now);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserMembershipDto.java b/sonar-db/src/main/java/org/sonar/db/user/UserMembershipDto.java
new file mode 100644
index 00000000000..14b3dd3c90b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserMembershipDto.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class UserMembershipDto {
+
+ private Long id;
+ private Long groupId;
+ private String login;
+ private String name;
+
+ public Long getId() {
+ return id;
+ }
+
+ public UserMembershipDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public UserMembershipDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @CheckForNull
+ public String getLogin() {
+ return login;
+ }
+
+ public UserMembershipDto setLogin(@Nullable String login) {
+ this.login = login;
+ return this;
+ }
+
+ @CheckForNull
+ public Long getGroupId() {
+ return groupId;
+ }
+
+ public UserMembershipDto setGroupId(@Nullable Long groupId) {
+ this.groupId = groupId;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserMembershipQuery.java b/sonar-db/src/main/java/org/sonar/db/user/UserMembershipQuery.java
new file mode 100644
index 00000000000..f89d99ce0cc
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserMembershipQuery.java
@@ -0,0 +1,164 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class UserMembershipQuery {
+
+ public static final int DEFAULT_PAGE_INDEX = 1;
+ public static final int DEFAULT_PAGE_SIZE = 100;
+
+ public static final String ANY = "ANY";
+ public static final String IN = "IN";
+ public static final String OUT = "OUT";
+ public static final Set<String> AVAILABLE_MEMBERSHIPS = ImmutableSet.of(ANY, IN, OUT);
+
+ private final Long groupId;
+ private final String membership;
+
+ private final String memberSearch;
+
+ // for internal use in MyBatis
+ final String memberSearchSql;
+
+ // max results per page
+ private final int pageSize;
+
+ // index of selected page. Start with 1.
+ private final int pageIndex;
+
+ private UserMembershipQuery(Builder builder) {
+ this.groupId = builder.groupId;
+ this.membership = builder.membership;
+ this.memberSearch = builder.memberSearch;
+ this.memberSearchSql = memberSearchToSql(memberSearch);
+
+ this.pageSize = builder.pageSize;
+ this.pageIndex = builder.pageIndex;
+ }
+
+ private String memberSearchToSql(@Nullable String s) {
+ String sql = null;
+ if (s != null) {
+ sql = StringUtils.replace(StringUtils.upperCase(s), "%", "/%");
+ sql = StringUtils.replace(sql, "_", "/_");
+ sql = "%" + sql + "%";
+ }
+ return sql;
+ }
+
+ public Long groupId() {
+ return groupId;
+ }
+
+ @CheckForNull
+ public String membership() {
+ return membership;
+ }
+
+ /**
+ * Search for users names/logins containing a given string
+ */
+ @CheckForNull
+ public String memberSearch() {
+ return memberSearch;
+ }
+
+ public int pageSize() {
+ return pageSize;
+ }
+
+ public int pageIndex() {
+ return pageIndex;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Long groupId;
+ private String membership;
+ private String memberSearch;
+
+ private Integer pageIndex = DEFAULT_PAGE_INDEX;
+ private Integer pageSize = DEFAULT_PAGE_SIZE;
+
+ private Builder() {
+ }
+
+ public Builder groupId(Long groupId) {
+ this.groupId = groupId;
+ return this;
+ }
+
+ public Builder membership(@Nullable String membership) {
+ this.membership = membership;
+ return this;
+ }
+
+ public Builder memberSearch(@Nullable String s) {
+ this.memberSearch = StringUtils.defaultIfBlank(s, null);
+ return this;
+ }
+
+ public Builder pageSize(@Nullable Integer i) {
+ this.pageSize = i;
+ return this;
+ }
+
+ public Builder pageIndex(@Nullable Integer i) {
+ this.pageIndex = i;
+ return this;
+ }
+
+ private void initMembership() {
+ membership = firstNonNull(membership, ANY);
+ checkArgument(AVAILABLE_MEMBERSHIPS.contains(membership),
+ "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIPS);
+ }
+
+ private void initPageSize() {
+ pageSize = firstNonNull(pageSize, DEFAULT_PAGE_SIZE);
+ }
+
+ private void initPageIndex() {
+ pageIndex = firstNonNull(pageIndex, DEFAULT_PAGE_INDEX);
+ checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
+ }
+
+ public UserMembershipQuery build() {
+ checkNotNull(groupId, "Group ID cant be null.");
+ initMembership();
+ initPageIndex();
+ initPageSize();
+ return new UserMembershipQuery(this);
+ }
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserRoleDto.java b/sonar-db/src/main/java/org/sonar/db/user/UserRoleDto.java
new file mode 100644
index 00000000000..9ce973e7519
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserRoleDto.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import javax.annotation.Nullable;
+
+/**
+ * @since 3.2
+ */
+public class UserRoleDto {
+ private Long id;
+ private Long userId;
+ private Long resourceId;
+ private String role;
+
+ public Long getId() {
+ return id;
+ }
+
+ public UserRoleDto setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public UserRoleDto setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ @Nullable
+ public Long getResourceId() {
+ return resourceId;
+ }
+
+ public UserRoleDto setResourceId(@Nullable Long resourceId) {
+ this.resourceId = resourceId;
+ return this;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public UserRoleDto setRole(String role) {
+ this.role = role;
+ return this;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
new file mode 100644
index 00000000000..e8435f2b05b
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
@@ -0,0 +1,133 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.db.MyBatis;
+
+public class DatabaseVersion {
+
+ public static final int LAST_VERSION = 923;
+
+ /**
+ * List of all the tables.
+ * This list is hardcoded because we didn't succeed in using java.sql.DatabaseMetaData#getTables() in the same way
+ * for all the supported databases, particularly due to Oracle results.
+ */
+ public static final List<String> TABLES = ImmutableList.of(
+ "action_plans",
+ "active_dashboards",
+ "active_rules",
+ "active_rule_parameters",
+ "activities",
+ "analysis_reports",
+ "authors",
+ "characteristics",
+ "dashboards",
+ "duplications_index",
+ "events",
+ "file_sources",
+ "groups",
+ "groups_users",
+ "group_roles",
+ "issues",
+ "issue_changes",
+ "issue_filters",
+ "issue_filter_favourites",
+ "loaded_templates",
+ "manual_measures",
+ "measure_filters",
+ "measure_filter_favourites",
+ "metrics",
+ "notifications",
+ "permission_templates",
+ "perm_templates_users",
+ "perm_templates_groups",
+ "quality_gates",
+ "quality_gate_conditions",
+ "projects",
+ "project_links",
+ "project_measures",
+ "project_qprofiles",
+ "properties",
+ "resource_index",
+ "rules",
+ "rules_parameters",
+ "rules_profiles",
+ "semaphores",
+ "schema_migrations",
+ "snapshots",
+ "users",
+ "user_roles",
+ "widgets",
+ "widget_properties"
+ );
+ private MyBatis mybatis;
+
+ public DatabaseVersion(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ @VisibleForTesting
+ static Status getStatus(Integer currentVersion, int lastVersion) {
+ Status status = Status.FRESH_INSTALL;
+ if (currentVersion != null) {
+ if (currentVersion == lastVersion) {
+ status = Status.UP_TO_DATE;
+ } else if (currentVersion > lastVersion) {
+ status = Status.REQUIRES_DOWNGRADE;
+ } else {
+ status = Status.REQUIRES_UPGRADE;
+ }
+ }
+ return status;
+ }
+
+ public Status getStatus() {
+ return getStatus(getVersion(), LAST_VERSION);
+ }
+
+ public Integer getVersion() {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ List<Integer> versions = session.getMapper(SchemaMigrationMapper.class).selectVersions();
+ if (!versions.isEmpty()) {
+ Collections.sort(versions);
+ return versions.get(versions.size() - 1);
+ }
+ return null;
+ } catch (RuntimeException e) {
+ // The table SCHEMA_MIGRATIONS does not exist.
+ // Ignore this exception -> it will be created by Ruby on Rails migrations.
+ return null;
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ public enum Status {
+ UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationDto.java b/sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationDto.java
new file mode 100644
index 00000000000..c78b8457f97
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationDto.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+/**
+ * Maps the table SCHEMA_MIGRATIONS that is fed by Ruby on Rails Migrations
+ * @since 3.0
+ */
+public class SchemaMigrationDto {
+ private String version;// NOSONAR this field is assigned by MyBatis
+
+ public String getVersion() {
+ return version;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationMapper.java b/sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationMapper.java
new file mode 100644
index 00000000000..5b8a3645be2
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/SchemaMigrationMapper.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.util.List;
+
+public interface SchemaMigrationMapper {
+ List<Integer> selectVersions();
+
+ void insert(String version);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/package-info.java b/sonar-db/src/main/java/org/sonar/db/version/package-info.java
new file mode 100644
index 00000000000..f5be5b6d523
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.version;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v44/ChangeLog.java b/sonar-db/src/main/java/org/sonar/db/version/v44/ChangeLog.java
new file mode 100644
index 00000000000..6306bb7704c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v44/ChangeLog.java
@@ -0,0 +1,118 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v44;
+
+import java.util.Date;
+
+/**
+ * SONAR-5329
+ * Transition ActiveRuleChanges to ActivityLog
+ * <p/>
+ * Used in the Active Record Migration 548.
+ *
+ * @since 4.4
+ */
+public class ChangeLog {
+
+ private int id;
+ private Date createdAt;
+ private int severity;
+ private String paramKey;
+ private String paramValue;
+ private String userLogin;
+ private String ruleKey;
+ private String repository;
+ private String profileKey;
+
+ public ChangeLog() {
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getSeverity() {
+ return severity;
+ }
+
+ public void setSeverity(int severity) {
+ this.severity = severity;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public String getParamKey() {
+ return paramKey;
+ }
+
+ public void setParamKey(String paramKey) {
+ this.paramKey = paramKey;
+ }
+
+ public String getParamValue() {
+ return paramValue;
+ }
+
+ public void setParamValue(String paramValue) {
+ this.paramValue = paramValue;
+ }
+
+ public String getUserLogin() {
+ return userLogin;
+ }
+
+ public void setUserLogin(String userLogin) {
+ this.userLogin = userLogin;
+ }
+
+ public String getRuleKey() {
+ return ruleKey;
+ }
+
+ public void setRuleKey(String ruleKey) {
+ this.ruleKey = ruleKey;
+ }
+
+ public String getRepository() {
+ return repository;
+ }
+
+ public void setRepository(String repository) {
+ this.repository = repository;
+ }
+
+ public String getProfileKey() {
+ return profileKey;
+ }
+
+ public void setProfileKey(String profileKey) {
+ this.profileKey = profileKey;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v44/Migration44Mapper.java b/sonar-db/src/main/java/org/sonar/db/version/v44/Migration44Mapper.java
new file mode 100644
index 00000000000..875be56a080
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v44/Migration44Mapper.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v44;
+
+import java.util.Date;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+public interface Migration44Mapper {
+
+ // migration of measures "profile" and "profile_version"
+ List<ProfileMeasure> selectProfileMeasures();
+
+ @CheckForNull
+ Integer selectProfileVersion(long snapshotId);
+
+ @CheckForNull
+ Date selectProfileVersionDate(@Param("profileId") int profileId, @Param("profileVersion") int profileVersion);
+
+ void updateProfileMeasure(@Param("measureId") long measureId, @Param("json") String json);
+
+ void deleteProfileMeasure(long profileMeasureId);
+
+ @CheckForNull
+ QProfileDto44 selectProfileById(int id);
+
+ // creation of columns RULES_PROFILES.CREATED_AT and UPDATED_AT
+ List<QProfileDto44> selectAllProfiles();
+
+ @CheckForNull
+ Date selectProfileCreatedAt(int profileId);
+
+ @CheckForNull
+ Date selectProfileUpdatedAt(int profileId);
+
+ void updateProfileDates(@Param("profileId") int profileId,
+ @Param("createdAt") Date createdAt, @Param("updatedAt") Date updatedAt,
+ @Param("rulesUpdatedAt") String rulesUpdatedAt);
+
+ // migrate changeLog to Activities
+ List<ChangeLog> selectActiveRuleChange(@Nullable @Param("enabled") Boolean enabled);
+
+ List<Long> selectMeasuresOnDeletedQualityProfiles();
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v44/ProfileMeasure.java b/sonar-db/src/main/java/org/sonar/db/version/v44/ProfileMeasure.java
new file mode 100644
index 00000000000..c3663442a87
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v44/ProfileMeasure.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v44;
+
+public class ProfileMeasure {
+ private long id;
+ private int profileId;
+ private long snapshotId;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public int getProfileId() {
+ return profileId;
+ }
+
+ public void setProfileId(int profileId) {
+ this.profileId = profileId;
+ }
+
+ public long getSnapshotId() {
+ return snapshotId;
+ }
+
+ public void setSnapshotId(long snapshotId) {
+ this.snapshotId = snapshotId;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v44/QProfileDto44.java b/sonar-db/src/main/java/org/sonar/db/version/v44/QProfileDto44.java
new file mode 100644
index 00000000000..7b659f52f13
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v44/QProfileDto44.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v44;
+
+public class QProfileDto44 {
+
+ private Integer id;
+ private String kee;
+ private String name;
+ private String language;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getKee() {
+ return kee;
+ }
+
+ public void setKee(String kee) {
+ this.kee = kee;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v44/package-info.java b/sonar-db/src/main/java/org/sonar/db/version/v44/package-info.java
new file mode 100644
index 00000000000..9d07e1f584a
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v44/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.version.v44;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v45/Migration45Mapper.java b/sonar-db/src/main/java/org/sonar/db/version/v45/Migration45Mapper.java
new file mode 100644
index 00000000000..7d6cac507c7
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v45/Migration45Mapper.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v45;
+
+import java.util.Date;
+import java.util.List;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Result;
+import org.apache.ibatis.annotations.Select;
+
+public interface Migration45Mapper {
+
+ @Select("SELECT rules_parameters.id, rules_parameters.rule_id as \"ruleId\", rules_parameters.name as \"name\", rules_parameters.param_type as \"type\", " +
+ " rules_parameters.default_value as \"defaultValue\", rules_parameters.description, rules.template_id as \"ruleTemplateId\" " +
+ "FROM rules_parameters " +
+ " INNER JOIN rules ON rules.id = rules_parameters.rule_id " +
+ "WHERE rules.is_template = ${_true}")
+ @Result(javaType = RuleParameter.class)
+ List<RuleParameter> selectAllTemplateRuleParameters();
+
+ @Select("SELECT rules_parameters.id, rules_parameters.rule_id as \"ruleId\", rules_parameters.name as \"name\", rules_parameters.param_type as \"type\", " +
+ " rules_parameters.default_value as \"defaultValue\", rules_parameters.description, rules.template_id as \"ruleTemplateId\" " +
+ "FROM rules_parameters " +
+ " INNER JOIN rules ON rules.id = rules_parameters.rule_id " +
+ "WHERE rules.template_id IS NOT NULL")
+ @Result(javaType = RuleParameter.class)
+ List<RuleParameter> selectAllCustomRuleParameters();
+
+ @Select("SELECT id, plugin_rule_key as \"ruleKey\", plugin_name as \"repositoryKey\", is_template as \"isTemplate\", template_id as \"templateId\"" +
+ "FROM rules " +
+ "WHERE rules.template_id IS NOT NULL")
+ @Result(javaType = Rule.class)
+ List<Rule> selectAllCustomRules();
+
+ @Insert("INSERT INTO rules_parameters (rule_id, name, param_type, default_value, description)" +
+ " VALUES (#{ruleId}, #{name}, #{type}, #{defaultValue}, #{description})")
+ @Options(useGeneratedKeys = false)
+ void insertRuleParameter(RuleParameter ruleParameter);
+
+ @Insert("UPDATE rules SET updated_at=#{date} WHERE id=#{id}")
+ @Options(useGeneratedKeys = false)
+ void updateRuleUpdateAt(@Param("id") Integer ruleId, @Param("date") Date updatedAt);
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v45/Rule.java b/sonar-db/src/main/java/org/sonar/db/version/v45/Rule.java
new file mode 100644
index 00000000000..18fcf353e78
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v45/Rule.java
@@ -0,0 +1,111 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v45;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ * SONAR-5575
+ * <p/>
+ * Used in the Active Record Migration 601.
+ *
+ * @since 4.5
+ */
+public final class Rule {
+
+ private Integer id;
+ private String repositoryKey;
+ private String ruleKey;
+ private boolean isTemplate;
+ private Integer templateId;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public Rule setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getRepositoryKey() {
+ return repositoryKey;
+ }
+
+ public Rule setRepositoryKey(String repositoryKey) {
+ this.repositoryKey = repositoryKey;
+ return this;
+ }
+
+ public String getRuleKey() {
+ return ruleKey;
+ }
+
+ public Rule setRuleKey(String ruleKey) {
+ this.ruleKey = ruleKey;
+ return this;
+ }
+
+ public boolean isTemplate() {
+ return isTemplate;
+ }
+
+ public Rule setIsTemplate(boolean isTemplate) {
+ this.isTemplate = isTemplate;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getTemplateId() {
+ return templateId;
+ }
+
+ public Rule setTemplateId(@Nullable Integer templateId) {
+ this.templateId = templateId;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Rule)) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ Rule other = (Rule) obj;
+ return new EqualsBuilder()
+ .append(repositoryKey, other.getRepositoryKey())
+ .append(ruleKey, other.getRuleKey())
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(repositoryKey)
+ .append(ruleKey)
+ .toHashCode();
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v45/RuleParameter.java b/sonar-db/src/main/java/org/sonar/db/version/v45/RuleParameter.java
new file mode 100644
index 00000000000..e2102b23bab
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v45/RuleParameter.java
@@ -0,0 +1,113 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v45;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+/**
+ * SONAR-5575
+ * <p/>
+ * Used in the Active Record Migration 601.
+ *
+ * @since 4.5
+ */
+public class RuleParameter {
+
+ private Integer id;
+ private Integer ruleId;
+ private Integer ruleTemplateId;
+ private String name;
+ private String type;
+ private String defaultValue;
+ private String description;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public RuleParameter setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public Integer getRuleId() {
+ return ruleId;
+ }
+
+ public RuleParameter setRuleId(Integer ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ @CheckForNull
+ public Integer getRuleTemplateId() {
+ return ruleTemplateId;
+ }
+
+ public RuleParameter setRuleTemplateId(@Nullable Integer ruleTemplateId) {
+ this.ruleTemplateId = ruleTemplateId;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public RuleParameter setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public RuleParameter setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ @CheckForNull
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public RuleParameter setDefaultValue(@Nullable String defaultValue) {
+ this.defaultValue = defaultValue;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public RuleParameter setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v45/package-info.java b/sonar-db/src/main/java/org/sonar/db/version/v45/package-info.java
new file mode 100644
index 00000000000..98d09760f72
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v45/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.version.v45;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/Component.java b/sonar-db/src/main/java/org/sonar/db/version/v50/Component.java
new file mode 100644
index 00000000000..acfa141ea9a
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/Component.java
@@ -0,0 +1,121 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class Component {
+
+ private Long id;
+ private Long projectId;
+ private Long snapshotId;
+ private String snapshotPath;
+ private String scope;
+
+ private String uuid;
+ private String projectUuid;
+ private String moduleUuid;
+ private String moduleUuidPath = "";
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ /**
+ * Can be null on provisioned projects or library
+ */
+ @CheckForNull
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public void setProjectId(@Nullable Long projectId) {
+ this.projectId = projectId;
+ }
+
+ /**
+ * Can be null on provisioned projects or library
+ */
+ @CheckForNull
+ public Long getSnapshotId() {
+ return snapshotId;
+ }
+
+ public void setSnapshotId(@Nullable Long snapshotId) {
+ this.snapshotId = snapshotId;
+ }
+
+ @CheckForNull
+ public String getSnapshotPath() {
+ return snapshotPath;
+ }
+
+ public void setSnapshotPath(@Nullable String snapshotPath) {
+ this.snapshotPath = snapshotPath;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public void setScope(String scope) {
+ this.scope = scope;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public void setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ }
+
+ @CheckForNull
+ public String getModuleUuid() {
+ return moduleUuid;
+ }
+
+ public void setModuleUuid(@Nullable String moduleUuid) {
+ this.moduleUuid = moduleUuid;
+ }
+
+ @CheckForNull
+ public String getModuleUuidPath() {
+ return moduleUuidPath;
+ }
+
+ public void setModuleUuidPath(@Nullable String moduleUuidPath) {
+ this.moduleUuidPath = moduleUuidPath;
+ }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/Migration50Mapper.java b/sonar-db/src/main/java/org/sonar/db/version/v50/Migration50Mapper.java
new file mode 100644
index 00000000000..f8f60aea16c
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/Migration50Mapper.java
@@ -0,0 +1,128 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Result;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.apache.ibatis.mapping.ResultSetType;
+
+public interface Migration50Mapper {
+
+ /**
+ * Return root projects (Views and Developers are NOT returned)
+ */
+ @Select("SELECT " +
+ " p.id AS \"id\", " +
+ " p.uuid AS \"uuid\", " +
+ " p.project_uuid AS \"projectUuid\", " +
+ " s.root_project_id AS \"projectId\", " +
+ " s.id AS \"snapshotId\", " +
+ " s.path AS \"snapshotPath\", " +
+ " p.scope AS \"scope\" " +
+ "FROM projects p " +
+ " LEFT OUTER JOIN snapshots s ON s.project_id = p.id AND s.islast = ${_true} " +
+ " WHERE " +
+ " p.scope = 'PRJ' " +
+ " AND p.root_id IS NULL ")
+ @Result(javaType = Component.class)
+ @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 200)
+ List<Component> selectRootProjects();
+
+ @Select("SELECT " +
+ " p.id AS \"id\", " +
+ " p.uuid AS \"uuid\", " +
+ " p.project_uuid AS \"projectUuid\", " +
+ " p.module_uuid AS \"moduleUuid\", " +
+ " p.module_uuid_path AS \"moduleUuidPath\", " +
+ " s.root_project_id AS \"projectId\", " +
+ " s.id AS \"snapshotId\", " +
+ " s.path AS \"snapshotPath\", " +
+ " p.scope AS \"scope\" " +
+ "FROM projects root " +
+ " INNER JOIN snapshots root_snapshot ON root_snapshot.project_id = root.id AND root_snapshot.islast = ${_true} " +
+ " INNER JOIN snapshots s ON s.root_snapshot_id = root_snapshot.id AND s.islast = ${_true} " +
+ " INNER JOIN projects p ON p.id = s.project_id " +
+ " WHERE root.id = #{id} ")
+ @Result(javaType = Component.class)
+ List<Component> selectComponentChildrenForProjects(@Param("id") Long projectId);
+
+ /**
+ * Return disabled direct children from a project (1st level modules, files on single module project)
+ * For migration re-entrance, ignore components already having UUID
+ */
+ @Select("SELECT " +
+ " p.id AS \"id\", " +
+ " p.uuid AS \"uuid\" " +
+ "FROM projects p " +
+ " INNER JOIN projects root ON root.id = p.root_id " +
+ " WHERE root.id = #{id} " +
+ " AND p.uuid IS NULL " +
+ " AND p.enabled=${_false} ")
+ @Result(javaType = Component.class)
+ List<Component> selectDisabledDirectComponentChildrenForProjects(@Param("id") Long projectId);
+
+ /**
+ * Return disabled none direct children (2nd level modules and more, files on modules, etc.)
+ * For migration re-entrance, ignore components already having UUID
+ */
+ @Select("SELECT " +
+ " p.id AS \"id\", " +
+ " p.uuid AS \"uuid\" " +
+ "FROM projects p " +
+ " INNER JOIN projects root_one ON root_one.id = p.root_id " +
+ " INNER JOIN projects root_two ON root_two.id = root_one.root_id " +
+ " WHERE root_two.id=#{id} " +
+ " AND p.uuid IS NULL " +
+ " AND p.enabled=${_false} ")
+ @Result(javaType = Component.class)
+ List<Component> selectDisabledNoneDirectComponentChildrenForProjects(@Param("id") Long projectId);
+
+ /**
+ * Return not migrated components
+ */
+ @Select("SELECT " +
+ " p.id AS \"id\" " +
+ "FROM projects p " +
+ " WHERE p.uuid IS NULL ")
+ @Result(javaType = Component.class)
+ List<Component> selectComponentsWithoutUuid();
+
+ @Select("SELECT " +
+ " p.id AS \"id\", " +
+ " p.uuid AS \"uuid\", " +
+ " p.project_uuid AS \"projectUuid\", " +
+ " p.module_uuid AS \"moduleUuid\", " +
+ " p.module_uuid_path AS \"moduleUuidPath\" " +
+ "FROM projects p " +
+ " WHERE p.kee = #{key}")
+ @Result(javaType = Component.class)
+ Component selectComponentByKey(@Param("key") String key);
+
+ @Update("UPDATE projects " +
+ " SET uuid=#{uuid}, project_uuid=#{projectUuid}, module_uuid=#{moduleUuid}, module_uuid_path=#{moduleUuidPath} " +
+ " WHERE id=#{id}")
+ @Options(useGeneratedKeys = false)
+ void updateComponentUuids(Component component);
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v50/package-info.java b/sonar-db/src/main/java/org/sonar/db/version/v50/package-info.java
new file mode 100644
index 00000000000..03bb5056ad3
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v50/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.db.version.v50;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/IsAliveMapper.xml b/sonar-db/src/main/resources/org/sonar/db/IsAliveMapper.xml
new file mode 100644
index 00000000000..c26b5961f8a
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/IsAliveMapper.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.IsAliveMapper">
+
+ <select id="isAlive" resultType="int">
+ select 1
+ <choose>
+ <when test="_databaseId == 'oracle'">
+ from dual
+ </when>
+ </choose>
+ </select>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/activity/ActivityMapper.xml b/sonar-db/src/main/resources/org/sonar/db/activity/ActivityMapper.xml
new file mode 100644
index 00000000000..0014464aa2c
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/activity/ActivityMapper.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.activity.ActivityMapper">
+
+ <insert id="insert" parameterType="Activity" useGeneratedKeys="false">
+ insert into activities
+ (created_at, log_key, log_type, log_action, user_login, data_field, log_message)
+ values (#{createdAt,jdbcType=TIMESTAMP}, #{key,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
+ #{action,jdbcType=VARCHAR},
+ #{author,jdbcType=VARCHAR}, #{data,jdbcType=VARCHAR}, #{message,jdbcType=VARCHAR})
+ </insert>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ComponentIndexMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ComponentIndexMapper.xml
new file mode 100644
index 00000000000..2a308781795
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ComponentIndexMapper.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.sonar.db.component.ComponentIndexMapper">
+
+ <select id="selectProjectIdsFromQueryAndViewOrSubViewUuid" parameterType="map" resultType="long">
+ SELECT r.resource_id FROM resource_index r
+ INNER JOIN projects copy ON copy.copy_resource_id = r.resource_id
+ <where>
+ AND copy.module_uuid_path LIKE #{viewUuidQuery}
+ AND r.kee LIKE #{query}
+ </where>
+ ORDER BY r.name_size
+ </select>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ComponentLinkMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ComponentLinkMapper.xml
new file mode 100644
index 00000000000..c6db203313b
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ComponentLinkMapper.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.sonar.db.component.ComponentLinkMapper">
+
+ <sql id="componentLinkColumns">
+ p.id,
+ p.component_uuid as "componentUuid",
+ p.link_type as "type",
+ p.name as name,
+ p.href as href
+ </sql>
+
+ <select id="selectByComponentUuid" parameterType="String" resultType="ComponentLink">
+ SELECT
+ <include refid="componentLinkColumns"/>
+ FROM project_links p
+ <where>
+ AND p.component_uuid=#{uuid}
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="ComponentLink" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO project_links (component_uuid, link_type, name, href)
+ VALUES (#{componentUuid,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
+ #{href,jdbcType=VARCHAR})
+ </insert>
+
+ <insert id="update" parameterType="ComponentLink" useGeneratedKeys="false">
+ UPDATE project_links SET component_uuid=#{componentUuid,jdbcType=VARCHAR}, link_type=#{type,jdbcType=VARCHAR},
+ name=#{name,jdbcType=VARCHAR}, href=#{href,jdbcType=VARCHAR}
+ WHERE id=#{id}
+ </insert>
+
+ <delete id="delete">
+ DELETE FROM project_links WHERE id=#{id}
+ </delete>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
new file mode 100644
index 00000000000..19740c7f76e
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
@@ -0,0 +1,315 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.sonar.db.component.ComponentMapper">
+
+ <sql id="componentColumns">
+ p.id,
+ p.uuid as uuid,
+ p.project_uuid as projectUuid,
+ p.module_uuid as moduleUuid,
+ p.module_uuid_path as moduleUuidPath,
+ p.kee as kee,
+ p.deprecated_kee as deprecatedKey,
+ p.name as name,
+ p.long_name as longName,
+ p.description as description,
+ p.qualifier as qualifier,
+ p.scope as scope,
+ p.language as language,
+ p.root_id as parentProjectId,
+ p.path as path,
+ p.enabled as enabled,
+ p.copy_resource_id as copyResourceId,
+ p.authorization_updated_at as authorizationUpdatedAt,
+ p.created_at as createdAt
+ </sql>
+
+ <sql id="authorizedComponentColumns">
+ p.id,
+ p.uuid as uuid,
+ p.kee as kee,
+ p.qualifier as qualifier,
+ p.scope as scope
+ </sql>
+
+ <select id="selectByKey" parameterType="String" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM projects p
+ <where>
+ AND p.kee=#{key}
+ </where>
+ </select>
+
+ <select id="selectById" parameterType="long" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM projects p
+ <where>
+ AND p.id=#{id}
+ </where>
+ </select>
+
+ <select id="selectByUuid" parameterType="String" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM projects p
+ <where>
+ AND p.uuid=#{uuid}
+ </where>
+ </select>
+
+ <select id="countById" parameterType="long" resultType="long">
+ SELECT count(p.id)
+ FROM projects p
+ <where>
+ AND p.id=#{id}
+ </where>
+ </select>
+
+ <select id="selectByKeys" parameterType="String" resultType="Component">
+ select
+ <include refid="componentColumns"/>
+ from projects p
+ <where>
+ p.enabled=${_true}
+ and p.kee in
+ <foreach collection="keys" open="(" close=")" item="key" separator=",">
+ #{key}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectByIds" parameterType="long" resultType="Component">
+ select
+ <include refid="componentColumns"/>
+ from projects p
+ <where>
+ p.enabled=${_true}
+ and p.id in
+ <foreach collection="ids" open="(" close=")" item="id" separator=",">
+ #{id}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectByUuids" parameterType="String" resultType="Component">
+ select
+ <include refid="componentColumns"/>
+ from projects p
+ <where>
+ and p.uuid in
+ <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">
+ #{uuid}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectExistingUuids" parameterType="String" resultType="String">
+ select p.uuid
+ from projects p
+ <where>
+ and p.uuid in
+ <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">
+ #{uuid}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectSubProjectsByComponentUuids" parameterType="String" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM projects p
+ INNER JOIN projects child ON child.root_id=p.id AND child.enabled=${_true}
+ <where>
+ AND p.enabled=${_true}
+ AND p.scope='PRJ'
+ AND child.uuid in
+ <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">
+ #{uuid}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectDescendantModules" parameterType="map" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM projects p
+ <include refid="modulesTreeQuery"/>
+ </select>
+
+ <sql id="modulesTreeQuery">
+ INNER JOIN projects module ON module.project_uuid = p.project_uuid AND module.uuid = #{moduleUuid} AND
+ module.scope='PRJ' AND module.enabled = ${_true}
+ <where>
+ <if test="excludeDisabled">
+ p.enabled = ${_true}
+ </if>
+ AND p.scope = #{scope}
+ AND
+ <choose>
+ <when test="_databaseId == 'mssql'">
+ p.module_uuid_path LIKE module.module_uuid_path + '%'
+ </when>
+ <when test="_databaseId == 'mysql'">
+ p.module_uuid_path LIKE concat(module.module_uuid_path, '%')
+ </when>
+ <otherwise>
+ p.module_uuid_path LIKE module.module_uuid_path || '%'
+ </otherwise>
+ </choose>
+ </where>
+ </sql>
+
+ <select id="selectEnabledFilesFromProject" parameterType="map" resultType="FilePathWithHash">
+ SELECT p.uuid, p.path, p.module_uuid as moduleUuid, fs.src_hash as srcHash
+ FROM projects p
+ INNER JOIN file_sources fs ON fs.file_uuid=p.uuid
+ <where>
+ AND p.project_uuid=#{projectUuid}
+ AND p.enabled=${_true}
+ AND p.scope='FIL'
+ </where>
+ </select>
+
+ <select id="selectDescendantFiles" parameterType="map" resultType="FilePathWithHash">
+ SELECT p.uuid, p.path, p.module_uuid as moduleUuid, fs.src_hash as srcHash
+ FROM projects p
+ INNER JOIN file_sources fs ON fs.file_uuid=p.uuid and fs.data_type='SOURCE'
+ <include refid="modulesTreeQuery"/>
+ </select>
+
+ <select id="selectProjectUuids" resultType="String">
+ SELECT p.uuid
+ FROM projects p
+ <where>
+ AND p.enabled=${_true}
+ AND p.scope='PRJ'
+ AND p.qualifier='TRK'
+ </where>
+ </select>
+
+ <select id="selectUuidsForQualifiers" resultType="UuidWithProjectUuid">
+ SELECT p.uuid as "uuid", p.project_uuid as "projectUuid" FROM projects p
+ INNER JOIN snapshots s on s.project_id=p.id AND s.islast=${_true}
+ <where>
+ p.enabled=${_true} AND
+ <foreach collection="qualifiers" open="(" close=")" item="qualifier" separator="OR ">
+ s.qualifier=#{qualifier}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectProjectsFromView" resultType="String">
+ SELECT p.uuid FROM projects technical_projects
+ INNER JOIN projects p on p.id=technical_projects.copy_resource_id AND p.enabled=${_true}
+ <where>
+ technical_projects.enabled=${_true} AND technical_projects.project_uuid=#{projectViewUuid}
+ AND technical_projects.module_uuid_path LIKE #{viewUuidLikeQuery}
+ </where>
+ </select>
+
+ <select id="selectComponentsFromProjectKeyAndScope" parameterType="map" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM projects p
+ INNER JOIN projects root ON root.uuid=p.project_uuid AND root.kee=#{projectKey}
+ <where>
+ AND p.enabled=${_true}
+ <if test="scope != null">
+ AND p.scope=#{scope}
+ </if>
+ </where>
+ </select>
+
+ <select id="selectProvisionedProjects" parameterType="map" resultType="Component">
+ select
+ <include refid="componentColumns"/>
+ from projects p
+ <include refid="provisionClauses"/>
+ </select>
+
+ <select id="countProvisionedProjects" parameterType="map" resultType="int">
+ select count(p.id)
+ from projects p
+ <include refid="provisionClauses"/>
+ </select>
+
+ <sql id="provisionClauses">
+ left join snapshots s on s.project_id=p.id
+ where
+ s.id is null
+ and p.enabled=${_true}
+ and p.qualifier=#{qualifier}
+ and p.copy_resource_id is null
+ <if test="query!=null">
+ and (
+ UPPER(p.name) like #{query}
+ or UPPER(p.kee) like #{query}
+ )
+ </if>
+ </sql>
+
+ <select id="selectGhostProjects" parameterType="map" resultType="Component">
+ select distinct
+ <include refid="componentColumns"/>
+ from projects p
+ <include refid="ghostClauses"/>
+ </select>
+
+ <select id="countGhostProjects" parameterType="map" resultType="long">
+ select count(p.id)
+ from projects p
+ <include refid="ghostClauses"/>
+ </select>
+
+ <sql id="ghostClauses">
+ inner join snapshots s1 on s1.project_id = p.id and s1.status='U'
+ left join snapshots s2 on s2.project_id = p.id and s2.status='P'
+ where
+ s2.id is null
+ and p.qualifier=#{qualifier}
+ and p.copy_resource_id is null
+ <if test="query!=null">
+ and (
+ UPPER(p.name) like #{query}
+ or UPPER(p.kee) like #{query}
+ )
+ </if>
+ </sql>
+
+ <insert id="insert" parameterType="Component" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO projects (kee, deprecated_kee, uuid, project_uuid, module_uuid, module_uuid_path, name, long_name,
+ qualifier, scope, language, description, root_id, path, copy_resource_id, enabled,
+ created_at, authorization_updated_at)
+ VALUES (#{kee,jdbcType=VARCHAR}, #{deprecatedKey,jdbcType=VARCHAR}, #{uuid,jdbcType=VARCHAR},
+ #{projectUuid,jdbcType=VARCHAR}, #{moduleUuid,jdbcType=VARCHAR}, #{moduleUuidPath,jdbcType=VARCHAR},
+ #{name,jdbcType=VARCHAR}, #{longName,jdbcType=VARCHAR}, #{qualifier,jdbcType=VARCHAR}, #{scope,jdbcType=VARCHAR},
+ #{language,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR},
+ #{parentProjectId,jdbcType=BIGINT}, #{path,jdbcType=VARCHAR}, #{copyResourceId,jdbcType=BIGINT},
+ #{enabled,jdbcType=BOOLEAN},
+ #{createdAt,jdbcType=TIMESTAMP}, #{authorizationUpdatedAt,jdbcType=BIGINT})
+ </insert>
+
+ <insert id="update" parameterType="Component" useGeneratedKeys="false">
+ UPDATE projects SET
+ kee=#{kee,jdbcType=VARCHAR},
+ deprecated_kee=#{deprecatedKey,jdbcType=VARCHAR},
+ project_uuid=#{projectUuid,jdbcType=VARCHAR},
+ module_uuid=#{moduleUuid,jdbcType=VARCHAR},
+ module_uuid_path=#{moduleUuidPath,jdbcType=VARCHAR},
+ name=#{name,jdbcType=VARCHAR},
+ long_name=#{longName,jdbcType=VARCHAR},
+ qualifier=#{qualifier,jdbcType=VARCHAR},
+ scope=#{scope,jdbcType=VARCHAR},
+ language=#{language,jdbcType=VARCHAR},
+ description=#{description,jdbcType=VARCHAR},
+ root_id=#{parentProjectId,jdbcType=BIGINT},
+ path=#{path,jdbcType=VARCHAR},
+ copy_resource_id=#{copyResourceId,jdbcType=BIGINT},
+ enabled=#{enabled,jdbcType=BOOLEAN},
+ authorization_updated_at=#{authorizationUpdatedAt,jdbcType=BIGINT}
+ WHERE uuid=#{uuid}
+ </insert>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ResourceIndexerMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ResourceIndexerMapper.xml
new file mode 100644
index 00000000000..82afc7fa85d
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ResourceIndexerMapper.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.component.ResourceIndexerMapper">
+
+ <!--
+ The column PROJECTS.ROOT_ID is not exact on multi-modules projects. The root id must
+ be loaded from the table SNAPSHOTS
+ -->
+ <select id="selectResources" parameterType="map" resultType="Resource">
+ select p.name as "name", p.id as "id", p.scope as "scope", p.qualifier as "qualifier", s.root_project_id as "rootId"
+ from projects p, snapshots s
+ <where>
+ p.enabled=${_true}
+ and p.copy_resource_id is null
+ and p.id=s.project_id
+ and s.islast=${_true}
+ <if test="scopes != null">
+ and p.scope in
+ <foreach item="scope" index="index" collection="scopes" open="(" separator="," close=")">#{scope}</foreach>
+ </if>
+ <if test="qualifiers != null">
+ and p.qualifier in
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator="," close=")">#{qualifier}
+ </foreach>
+ </if>
+ <if test="rootProjectId != null">
+ and s.root_project_id=#{rootProjectId}
+ </if>
+ <if test="nonIndexedOnly">
+ and not exists(select * from resource_index ri where ri.resource_id=p.id)
+ </if>
+ </where>
+ order by p.id
+ </select>
+
+ <select id="selectRootProjectIds" parameterType="map" resultType="int">
+ select distinct root_project_id
+ from snapshots
+ where islast=${_true}
+ and scope='PRJ'
+ and qualifier in ('TRK', 'VW', 'SVW')
+ </select>
+
+ <select id="selectMasterIndexByResourceId" parameterType="long" resultType="ResourceIndex">
+ select kee as "key", resource_id as "resourceId"
+ from resource_index
+ where resource_id=#{id} and position=0
+ </select>
+
+ <select id="selectResourceToIndex" parameterType="long" resultType="Resource">
+ select id, name, root_id as "rootId", qualifier
+ from projects
+ where id=#{id} and enabled=${_true}
+ </select>
+
+ <delete id="deleteByResourceId" parameterType="long">
+ delete from resource_index
+ where resource_id=#{id}
+ </delete>
+
+ <insert id="insert" parameterType="ResourceIndex" useGeneratedKeys="false">
+ insert into resource_index (kee, position, name_size, resource_id, root_project_id, qualifier)
+ values (#{key}, #{position}, #{nameSize},
+ #{resourceId}, #{rootProjectId}, #{qualifier})
+ </insert>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ResourceKeyUpdaterMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ResourceKeyUpdaterMapper.xml
new file mode 100644
index 00000000000..40add31c8cd
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ResourceKeyUpdaterMapper.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.component.ResourceKeyUpdaterMapper">
+
+ <resultMap id="resourceResultMap" type="Resource">
+ <id property="id" column="id"/>
+ <result property="key" column="kee"/>
+ <result property="deprecatedKey" column="deprecated_kee"/>
+ <result property="rootId" column="root_id"/>
+ <result property="scope" column="scope"/>
+ </resultMap>
+
+ <select id="countResourceByKey" parameterType="String" resultType="int">
+ SELECT count(*)
+ FROM projects
+ WHERE kee = #{key}
+ </select>
+
+ <select id="selectProject" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where id=#{id}
+ </select>
+
+ <select id="selectProjectResources" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where root_id=#{id} AND scope!='PRJ'
+ </select>
+
+ <select id="selectDescendantProjects" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where scope='PRJ' and root_id=#{id}
+ </select>
+
+ <update id="update" parameterType="Resource">
+ update projects
+ set kee = #{key}, deprecated_kee = #{deprecatedKey}
+ where id = #{id}
+ </update>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/ResourceMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/ResourceMapper.xml
new file mode 100644
index 00000000000..39339fe5ec2
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/ResourceMapper.xml
@@ -0,0 +1,255 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.component.ResourceMapper">
+
+ <resultMap id="snapshotResultMap" type="Snapshot">
+ <id property="id" column="id"/>
+ <result property="parentId" column="parent_snapshot_id"/>
+ <result property="rootId" column="root_snapshot_id"/>
+ <result property="createdAt" column="created_at"/>
+ <result property="buildDate" column="build_date"/>
+ <result property="componentId" column="project_id"/>
+ <result property="status" column="status"/>
+ <result property="purgeStatus" column="purge_status"/>
+ <result property="last" column="islast"/>
+ <result property="scope" column="scope"/>
+ <result property="qualifier" column="qualifier"/>
+ <result property="version" column="version"/>
+ <result property="path" column="path"/>
+ <result property="depth" column="depth"/>
+ <result property="rootProjectId" column="root_project_id"/>
+ <result property="period1Mode" column="period1_mode"/>
+ <result property="period2Mode" column="period2_mode"/>
+ <result property="period3Mode" column="period3_mode"/>
+ <result property="period4Mode" column="period4_mode"/>
+ <result property="period5Mode" column="period5_mode"/>
+ <result property="period1Param" column="period1_param"/>
+ <result property="period2Param" column="period2_param"/>
+ <result property="period3Param" column="period3_param"/>
+ <result property="period4Param" column="period4_param"/>
+ <result property="period5Param" column="period5_param"/>
+ <result property="period1Date" column="period1_date"/>
+ <result property="period2Date" column="period2_date"/>
+ <result property="period3Date" column="period3_date"/>
+ <result property="period4Date" column="period4_date"/>
+ <result property="period5Date" column="period5_date"/>
+ </resultMap>
+
+ <resultMap id="resourceResultMap" type="Resource">
+ <id property="id" column="id"/>
+ <result property="key" column="kee"/>
+ <result property="uuid" column="uuid"/>
+ <result property="projectUuid" column="project_uuid"/>
+ <result property="moduleUuid" column="module_uuid"/>
+ <result property="moduleUuidPath" column="module_uuid_path"/>
+ <result property="deprecatedKey" column="deprecated_kee"/>
+ <result property="path" column="path"/>
+ <result property="name" column="name"/>
+ <result property="longName" column="long_name"/>
+ <result property="rootId" column="root_id"/>
+ <result property="scope" column="scope"/>
+ <result property="qualifier" column="qualifier"/>
+ <result property="enabled" column="enabled"/>
+ <result property="description" column="description"/>
+ <result property="language" column="language"/>
+ <result property="copyResourceId" column="copy_resource_id"/>
+ <result property="personId" column="person_id"/>
+ <result property="createdAt" column="created_at"/>
+ <result property="authorizationUpdatedAt" column="authorization_updated_at"/>
+ </resultMap>
+
+ <select id="selectResources" parameterType="map" resultMap="resourceResultMap">
+ select * from projects p
+ <where>
+ <if test="qualifiers != null and qualifiers.length!=0">
+ and p.qualifier in
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator="," close=")">#{qualifier}
+ </foreach>
+ </if>
+ <if test="key != null">
+ and p.kee=#{key}
+ </if>
+ <if test="excludeDisabled">
+ and p.enabled=${_true}
+ </if>
+ </where>
+ </select>
+
+ <select id="selectResourceIds" parameterType="map" resultType="long">
+ select p.id
+ from projects p
+ <where>
+ <if test="qualifiers != null and qualifiers.length!=0">
+ and p.qualifier in
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator="," close=")">#{qualifier}
+ </foreach>
+ </if>
+ <if test="key != null">
+ and p.kee=#{key}
+ </if>
+ <if test="excludeDisabled">
+ and p.enabled=${_true}
+ </if>
+ </where>
+ </select>
+
+ <select id="selectResource" parameterType="long" resultMap="resourceResultMap">
+ select * from projects p
+ where p.id=#{id}
+ </select>
+
+ <select id="selectResourceByUuid" parameterType="String" resultMap="resourceResultMap">
+ select * from projects p
+ where p.uuid=#{uuid}
+ </select>
+
+ <select id="selectSnapshot" parameterType="long" resultMap="snapshotResultMap">
+ select * from snapshots where id=#{id}
+ </select>
+
+ <select id="selectLastSnapshotByResourceKey" parameterType="string" resultMap="snapshotResultMap">
+ SELECT s.* FROM snapshots s
+ INNER JOIN projects p on p.id=s.project_id AND p.enabled=${_true} AND p.copy_resource_id IS NULL
+ <where>
+ AND p.kee=#{id}
+ AND s.islast=${_true}
+ </where>
+ </select>
+
+ <select id="selectLastSnapshotByResourceUuid" parameterType="string" resultMap="snapshotResultMap">
+ SELECT s.* from snapshots s
+ INNER JOIN projects p on p.id=s.project_id AND p.enabled=${_true} AND p.copy_resource_id IS NULL
+ <where>
+ AND p.uuid=#{uuid}
+ AND s.islast=${_true}
+ </where>
+ </select>
+
+ <select id="selectDescendantProjects" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where scope='PRJ' and root_id=#{id}
+ </select>
+
+ <select id="selectRootProjectByComponentKey" parameterType="string" resultMap="resourceResultMap">
+ select rootProject.*
+ from projects p
+ inner join snapshots s on s.project_id=p.id and s.islast=${_true}
+ inner join projects rootProject on rootProject.id=s.root_project_id
+ <where>
+ and p.kee=#{componentKey}
+ </where>
+ </select>
+
+ <select id="selectRootProjectByComponentId" parameterType="long" resultMap="resourceResultMap">
+ select rootProject.*
+ from snapshots s
+ inner join projects rootProject on rootProject.id=s.root_project_id
+ where
+ s.project_id=#{componentId}
+ and s.islast=${_true}
+ </select>
+
+ <select id="selectProjectsIncludingNotCompletedOnesByQualifiers" parameterType="map" resultMap="resourceResultMap">
+ select * from projects p
+ <where>
+ <if test="qualifiers != null and qualifiers.size() > 0">
+ and
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator=" or " close=")">
+ p.qualifier=#{qualifier}
+ </foreach>
+ </if>
+ and p.enabled=${_true}
+ and p.copy_resource_id is null
+ </where>
+ </select>
+
+ <select id="selectProjectsByQualifiers" parameterType="map" resultMap="resourceResultMap">
+ <include refid="selectProjectsByQualifiersQuery"/>
+ </select>
+
+ <sql id="selectProjectsByQualifiersQuery">
+ select p.* from projects p
+ inner join snapshots s on s.project_id=p.id
+ <where>
+ <if test="qualifiers != null and qualifiers.size() > 0">
+ and
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator=" or " close=")">
+ p.qualifier=#{qualifier}
+ </foreach>
+ </if>
+ and p.enabled=${_true}
+ and p.copy_resource_id is null
+ and s.islast=${_true}
+ </where>
+ </sql>
+
+ <select id="selectGhostsProjects" parameterType="map" resultMap="resourceResultMap">
+ select distinct p.* from projects p
+ inner join snapshots s1 on s1.project_id = p.id and s1.status='U'
+ left join snapshots s2 on s2.project_id = p.id and s2.status='P'
+ <where>
+ and s2.id is null
+ <if test="qualifiers != null and qualifiers.size() > 0">
+ and
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator=" or " close=")">
+ p.qualifier=#{qualifier}
+ </foreach>
+ </if>
+ and p.copy_resource_id is null
+ </where>
+ </select>
+
+ <select id="selectProvisionedProjects" parameterType="map" resultMap="resourceResultMap">
+ select p.* from projects p
+ left join snapshots s on s.project_id=p.id
+ <where>
+ and s.id is null
+ <if test="qualifiers != null and qualifiers.size() > 0">
+ and
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator=" or " close=")">
+ p.qualifier=#{qualifier}
+ </foreach>
+ </if>
+ and p.copy_resource_id is null
+ </where>
+ </select>
+
+ <select id="selectProvisionedProject" parameterType="string" resultMap="resourceResultMap">
+ select p.* from projects p
+ left join snapshots s on s.project_id=p.id
+ where s.id is null
+ and p.kee = #{key}
+ and p.copy_resource_id is null
+ </select>
+
+ <insert id="insert" parameterType="Resource" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ insert into projects
+ (uuid, project_uuid, module_uuid, module_uuid_path, name, long_name, description, scope, qualifier, kee,
+ deprecated_kee, path, language, root_id, copy_resource_id, person_id,
+ enabled, authorization_updated_at, created_at)
+ values (
+ #{uuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{moduleUuid,jdbcType=VARCHAR},
+ #{moduleUuidPath,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
+ #{longName,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{scope,jdbcType=VARCHAR},
+ #{qualifier,jdbcType=VARCHAR},
+ #{key,jdbcType=VARCHAR}, #{deprecatedKey,jdbcType=VARCHAR}, #{path,jdbcType=VARCHAR}, #{language,jdbcType=VARCHAR},
+ #{rootId,jdbcType=INTEGER}, #{copyResourceId,jdbcType=INTEGER},
+ #{personId,jdbcType=INTEGER}, #{enabled,jdbcType=BOOLEAN}, #{authorizationUpdatedAt,jdbcType=BIGINT},
+ #{createdAt,jdbcType=TIMESTAMP}
+ )
+ </insert>
+
+ <update id="update" parameterType="Resource">
+ update projects set name=#{name}, long_name=#{longName}, description=#{description},
+ scope=#{scope}, qualifier=#{qualifier}, kee=#{key}, deprecated_kee=#{deprecatedKey}, path=#{path},
+ language=#{language}, root_id=#{rootId}, copy_resource_id=#{copyResourceId},
+ person_id=#{personId}, enabled=#{enabled} where id=#{id}
+ </update>
+
+ <update id="updateAuthorizationDate" parameterType="map">
+ update projects set authorization_updated_at=#{authorizationDate}
+ where id=#{projectId}
+ </update>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml
new file mode 100644
index 00000000000..95af7ba53a8
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.sonar.db.component.SnapshotMapper">
+
+ <sql id="snapshotColumns">
+ s.id,
+ s.parent_snapshot_id as parentId,
+ s.root_snapshot_id as rootId,
+ s.root_project_id as rootProjectId,
+ s.project_id as componentId,
+ s.created_at as createdAt,
+ s.build_date as buildDate,
+ s.status as status,
+ s.purge_status as purgeStatus,
+ s.islast as last,
+ s.scope as scope,
+ s.qualifier as qualifier,
+ s.version as version,
+ s.path as path,
+ s.depth as depth,
+ s.period1_mode as period1Mode,
+ s.period2_mode as period2Mode,
+ s.period3_mode as period3Mode,
+ s.period4_mode as period4Mode,
+ s.period5_mode as period5Mode,
+ s.period1_param as period1Param,
+ s.period2_param as period2Param,
+ s.period3_param as period3Param,
+ s.period4_param as period4Param,
+ s.period5_param as period5Param,
+ s.period1_date as period1Date,
+ s.period2_date as period2Date,
+ s.period3_date as period3Date,
+ s.period4_date as period4Date,
+ s.period5_date as period5Date
+ </sql>
+
+ <select id="selectByKey" parameterType="Long" resultType="Snapshot">
+ SELECT
+ <include refid="snapshotColumns"/>
+ FROM snapshots s
+ <where>
+ AND s.id=#{key}
+ </where>
+ </select>
+
+ <select id="selectLastSnapshot" resultType="Snapshot">
+ select
+ <include refid="snapshotColumns"/>
+ from snapshots s
+ where s.islast=${_true} and s.project_id = #{resource}
+ </select>
+
+ <select id="selectSnapshotsByQuery" parameterType="map" resultType="Snapshot">
+ SELECT
+ <include refid="snapshotColumns"/>
+ FROM snapshots s
+ <where>
+ <if test="query.componentId != null">
+ AND s.project_id=#{query.componentId}
+ </if>
+ <if test="query.status != null">
+ AND status=#{query.status}
+ </if>
+ <if test="query.version != null">
+ AND version=#{query.version}
+ </if>
+ <if test="query.isLast != null">
+ AND islast=#{query.isLast}
+ </if>
+ <if test="query.createdAfter != null">
+ AND created_at>=#{query.createdAfter}
+ </if>
+ <if test="query.createdBefore != null">
+ AND created_at&lt;#{query.createdBefore}
+ </if>
+ </where>
+ <if test="query.sortField != null">
+ ORDER BY
+ <if test="query.sortField == 'created_at'">
+ created_at
+ </if>
+ <if test="query.sortOrder == 'asc'">
+ asc
+ </if>
+ <if test="query.sortOrder == 'desc'">
+ desc
+ </if>
+ </if>
+ </select>
+
+ <select id="selectPreviousVersionSnapshots" parameterType="map" resultType="Snapshot">
+ SELECT
+ <include refid="snapshotColumns"/>
+ FROM snapshots s
+ INNER JOIN events e ON s.id = e.snapshot_id AND e.name &lt;&gt; #{lastVersion} AND e.category='Version'
+ INNER JOIN projects p ON p.uuid=e.component_uuid AND p.id=#{componentId}
+ ORDER BY e.event_date DESC
+ </select>
+
+ <select id="selectSnapshotAndChildrenOfScope" parameterType="map" resultType="Snapshot">
+ select
+ <include refid="snapshotColumns"/>
+ from snapshots s
+ where s.scope = #{scope}
+ AND (s.id = #{snapshot} or s.root_snapshot_id = #{snapshot})
+ </select>
+
+ <sql id="insertColumns">
+ (parent_snapshot_id, root_snapshot_id, root_project_id, project_id, created_at, build_date, status, purge_status,
+ islast, scope, qualifier, version, path, depth,
+ period1_mode, period2_mode, period3_mode, period4_mode, period5_mode,
+ period1_param, period2_param, period3_param, period4_param, period5_param,
+ period1_date, period2_date, period3_date, period4_date, period5_date)
+ </sql>
+
+ <update id="updateSnapshotAndChildrenLastFlagAndStatus" parameterType="map">
+ update snapshots
+ set islast = #{isLast}, status = #{status}
+ where root_snapshot_id=#{root} or id=#{root} or (path like #{path} and root_snapshot_id=#{pathRootId})
+ </update>
+
+ <update id="updateSnapshotAndChildrenLastFlag" parameterType="map">
+ update snapshots
+ set islast = #{isLast}
+ where root_snapshot_id=#{root} or id=#{root} or (path like #{path} and root_snapshot_id=#{pathRootId})
+ </update>
+
+ <insert id="insert" parameterType="Snapshot" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ insert into snapshots
+ <include refid="insertColumns"/>
+ values (#{parentId}, #{rootId}, #{rootProjectId}, #{componentId}, #{createdAt}, #{buildDate}, #{status},
+ #{purgeStatus}, #{last}, #{scope}, #{qualifier}, #{version}, #{path}, #{depth},
+ #{period1Mode}, #{period2Mode}, #{period3Mode}, #{period4Mode}, #{period5Mode},
+ #{period1Param}, #{period2Param}, #{period3Param}, #{period4Param}, #{period5Param},
+ #{period1Date}, #{period2Date}, #{period3Date}, #{period4Date}, #{period5Date})
+ </insert>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/compute/AnalysisReportMapper.xml b/sonar-db/src/main/resources/org/sonar/db/compute/AnalysisReportMapper.xml
new file mode 100644
index 00000000000..2a3700186d4
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/compute/AnalysisReportMapper.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.compute.AnalysisReportMapper">
+ <sql id="reportColumns">
+ <!-- the data report is not brought back by default as it could be too big in memory -->
+ ar.id,
+ ar.project_key as projectKey,
+ ar.project_name as projectName,
+ ar.report_status as status,
+ ar.uuid as uuid,
+ ar.created_at as createdAt,
+ ar.updated_at as updatedAt,
+ ar.started_at as startedAt,
+ ar.finished_at as finishedAt
+ </sql>
+
+ <insert id="insert" parameterType="AnalysisReport" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
+ insert into analysis_reports
+ (project_key, project_name, uuid, report_status, created_at, updated_at, started_at, finished_at)
+ values (
+ #{projectKey,jdbcType=VARCHAR}, #{projectName,jdbcType=VARCHAR}, #{uuid,jdbcType=VARCHAR},
+ #{status,jdbcType=VARCHAR},
+ #{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT}, #{startedAt,jdbcType=BIGINT},
+ #{finishedAt,jdbcType=BIGINT}
+ )
+ </insert>
+
+ <update id="resetAllToPendingStatus" parameterType="map">
+ update analysis_reports
+ set report_status='PENDING', updated_at=#{updatedAt,jdbcType=BIGINT}, started_at=NULL
+ </update>
+
+ <update id="updateWithBookingReport" parameterType="map">
+ update analysis_reports
+ set report_status=#{busyStatus,jdbcType=VARCHAR},
+ started_at=#{startedAt,jdbcType=BIGINT}
+ where id=#{id} and report_status=#{availableStatus}
+ </update>
+
+ <delete id="truncate">
+ truncate table analysis_reports
+ </delete>
+
+ <delete id="delete">
+ delete from analysis_reports where id=#{id}
+ </delete>
+
+ <select id="selectById" resultType="AnalysisReport">
+ select
+ <include refid="reportColumns"/>
+ from analysis_reports ar
+ where id = #{id}
+ </select>
+
+ <select id="selectByProjectKey" parameterType="String" resultType="AnalysisReport">
+ select
+ <include refid="reportColumns"/>
+ from analysis_reports ar
+ where project_key = #{projectKey}
+ </select>
+
+ <!-- TODO optimize by restricting results to first row (LIMIT 1 on most dbs) -->
+ <select id="selectAvailables" parameterType="map" resultType="Long">
+ select ar.id
+ from analysis_reports ar
+ where ar.report_status=#{availableStatus}
+ and not exists(
+ select 1
+ from analysis_reports ar2
+ where ar.project_key = ar2.project_key
+ and ar2.report_status=#{busyStatus}
+ )
+ order by ar.created_at asc, ar.id asc
+ </select>
+
+ <select id="selectAll" resultType="AnalysisReport">
+ select
+ <include refid="reportColumns"/>
+ from analysis_reports ar
+ </select>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/dashboard/ActiveDashboardMapper.xml b/sonar-db/src/main/resources/org/sonar/db/dashboard/ActiveDashboardMapper.xml
new file mode 100644
index 00000000000..6c88317e890
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/dashboard/ActiveDashboardMapper.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.dashboard.ActiveDashboardMapper">
+
+ <insert id="insert" parameterType="ActiveDashboard" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO active_dashboards (dashboard_id, user_id, order_index)
+ VALUES (#{dashboardId}, #{userId}, #{orderIndex})
+ </insert>
+
+ <select id="selectMaxOrderIndexForNullUser" resultType="Integer">
+ SELECT MAX(order_index)
+ FROM active_dashboards
+ WHERE user_id IS NULL
+ </select>
+
+ <sql id="dashboardColumns">
+ d.id, d.user_id as "userId", d.name, d.description, d.column_layout as "columnLayout",
+ d.shared, d.is_global as "global", d.created_at as "createdAt", d.updated_at as "updatedAt"
+ </sql>
+
+ <select id="selectGlobalDashboardsForUserLogin" parameterType="String" resultType="Dashboard">
+ SELECT
+ <include refid="dashboardColumns"/>
+ FROM dashboards d
+ INNER JOIN active_dashboards ad on d.id=ad.dashboard_id
+ LEFT OUTER JOIN users u on u.id=ad.user_id
+ WHERE d.is_global=${_true}
+ <choose>
+ <when test="login == null">
+ AND u.login IS NULL
+ </when>
+ <otherwise>
+ AND u.login=#{login}
+ </otherwise>
+ </choose>
+ ORDER BY order_index ASC
+ </select>
+
+ <select id="selectProjectDashboardsForUserLogin" parameterType="String" resultType="Dashboard">
+ SELECT
+ <include refid="dashboardColumns"/>
+ FROM dashboards d
+ INNER JOIN active_dashboards ad on d.id=ad.dashboard_id
+ LEFT OUTER JOIN users u on u.id=ad.user_id
+ WHERE d.is_global=${_false}
+ <choose>
+ <when test="login == null">
+ AND u.login IS NULL
+ </when>
+ <otherwise>
+ AND u.login=#{login}
+ </otherwise>
+ </choose>
+ ORDER BY order_index ASC
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/dashboard/DashboardMapper.xml b/sonar-db/src/main/resources/org/sonar/db/dashboard/DashboardMapper.xml
new file mode 100644
index 00000000000..ce27a44c73b
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/dashboard/DashboardMapper.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.dashboard.DashboardMapper">
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetMapper.xml b/sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetMapper.xml
new file mode 100644
index 00000000000..d77042a1d07
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetMapper.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.dashboard.WidgetMapper">
+
+ <sql id="selectColumns">
+ w.id as "id",
+ w.dashboard_id as "dashboardId",
+ w.widget_key as "widgetKey",
+ w.name as "name",
+ w.description as "description",
+ w.column_index as "columnIndex",
+ w.row_index as "rowIndex",
+ w.configured as "configured",
+ w.created_at as "createdAt",
+ w.updated_at as "updatedAt",
+ w.resource_id as "resourceId"
+ </sql>
+
+ <select id="selectById" parameterType="Integer" resultType="Widget">
+ select
+ <include refid="selectColumns"/>
+ from widgets w where w.id=#{id}
+ </select>
+
+ <select id="selectByDashboard" parameterType="Integer" resultType="Widget">
+ select
+ <include refid="selectColumns"/>
+ from widgets w where w.dashboard_id=#{id}
+ </select>
+
+ <select id="selectAll" resultType="Widget">
+ select
+ <include refid="selectColumns"/>
+ from widgets w
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetPropertyMapper.xml b/sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetPropertyMapper.xml
new file mode 100644
index 00000000000..a2bf8024a7f
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/dashboard/WidgetPropertyMapper.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.dashboard.WidgetPropertyMapper">
+
+ <delete id="deleteByWidgetIds" parameterType="map">
+ DELETE FROM widget_properties
+ WHERE widget_id IN
+ <foreach collection="list" open="(" close=")" item="wid" separator=",">#{wid}</foreach>
+ </delete>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/debt/CharacteristicMapper.xml b/sonar-db/src/main/resources/org/sonar/db/debt/CharacteristicMapper.xml
new file mode 100644
index 00000000000..a5f7aa7848a
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/debt/CharacteristicMapper.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.db.debt.CharacteristicMapper">
+
+ <sql id="characteristicColumns">
+ c.id,
+ c.kee as kee,
+ c.name as name,
+ c.parent_id as parentId,
+ c.characteristic_order as characteristicOrder,
+ c.enabled as enabled,
+ c.created_at as createdAt,
+ c.updated_at as updatedAt
+ </sql>
+
+ <select id="selectEnabledCharacteristics" parameterType="map" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ where c.enabled=${_true}
+ </select>
+
+ <select id="selectCharacteristics" parameterType="map" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ </select>
+
+ <select id="selectEnabledRootCharacteristics" parameterType="map" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ <where>
+ and c.parent_id is null
+ and c.enabled=${_true}
+ </where>
+ order by characteristic_order asc
+ </select>
+
+ <select id="selectCharacteristicsByParentId" parameterType="map" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ <where>
+ and c.parent_id=#{parent_id}
+ and c.enabled=${_true}
+ </where>
+ </select>
+
+ <select id="selectCharacteristicsByIds" parameterType="map" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ <where>
+ and c.enabled=${_true}
+ AND (<foreach item="id" index="index" collection="ids" open="(" separator=" or " close=")">c.id=#{id}</foreach>)
+ </where>
+ </select>
+
+ <select id="selectByKey" parameterType="String" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ <where>
+ and c.kee=#{key}
+ and c.enabled=${_true}
+ </where>
+ </select>
+
+ <select id="selectById" parameterType="Integer" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ <where>
+ and c.id=#{id}
+ and c.enabled=${_true}
+ </where>
+ </select>
+
+ <select id="selectByName" parameterType="String" resultType="Characteristic">
+ select
+ <include refid="characteristicColumns"/>
+ from characteristics c
+ <where>
+ and c.name=#{name}
+ and c.enabled=${_true}
+ </where>
+ </select>
+
+ <select id="selectMaxCharacteristicOrder" resultType="Integer">
+ select max(c.characteristic_order)
+ from characteristics c
+ <where>
+ and c.parent_id is null
+ and c.enabled=${_true}
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="Characteristic" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at, updated_at)
+ VALUES (#{kee,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{parentId,jdbcType=INTEGER},
+ #{characteristicOrder,jdbcType=INTEGER}, #{enabled,jdbcType=BOOLEAN}, #{createdAt,jdbcType=TIMESTAMP},
+ #{updatedAt,jdbcType=TIMESTAMP})
+ </insert>
+
+ <update id="update" parameterType="Characteristic">
+ update characteristics set
+ name=#{name},
+ parent_id=#{parentId},
+ characteristic_order=#{characteristicOrder},
+ enabled=#{enabled},
+ updated_at=#{updatedAt}
+ where id=#{id}
+ </update>
+
+ <select id="selectDeprecatedRequirements" resultType="RequirementMigration">
+ select id as "id",
+ parent_id as "parentId",
+ root_id as "rootId",
+ rule_id as "ruleId",
+ function_key as "functionKey",
+ factor_value as "coefficientValue",
+ factor_unit as "coefficientUnit",
+ offset_value as "offsetValue",
+ offset_unit as "offsetUnit",
+ enabled as "enabled"
+ from characteristics
+ where rule_id IS NOT NULL
+ </select>
+
+ <delete id="deleteRequirementsFromCharacteristicsTable">
+ DELETE FROM characteristics WHERE rule_id IS NOT NULL
+ </delete>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml b/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml
new file mode 100644
index 00000000000..66212b3b205
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/duplication/DuplicationMapper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.duplication.DuplicationMapper">
+
+ <select id="selectCandidates" parameterType="map" resultType="DuplicationUnit">
+ SELECT DISTINCT to_blocks.hash as hash, res.kee as resourceKey, to_blocks.index_in_file as indexInFile,
+ to_blocks.start_line as startLine, to_blocks.end_line as endLine
+ FROM duplications_index to_blocks, duplications_index from_blocks, snapshots snapshot, projects res
+ WHERE from_blocks.snapshot_id = #{resource_snapshot_id}
+ AND to_blocks.hash = from_blocks.hash
+ AND to_blocks.snapshot_id = snapshot.id
+ AND snapshot.islast = ${_true}
+ AND snapshot.project_id = res.id
+ AND res.language = #{language}
+ <if test="last_project_snapshot_id != null">
+ AND to_blocks.project_snapshot_id != #{last_project_snapshot_id}
+ </if>
+ </select>
+
+ <insert id="batchInsert" parameterType="DuplicationUnit" useGeneratedKeys="false">
+ INSERT INTO duplications_index (snapshot_id, project_snapshot_id, hash, index_in_file, start_line, end_line)
+ VALUES (#{snapshotId}, #{projectSnapshotId}, #{hash}, #{indexInFile}, #{startLine}, #{endLine})
+ </insert>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml b/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml
new file mode 100644
index 00000000000..fa30a1f766f
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.sonar.db.event.EventMapper">
+
+ <sql id="eventColumns">
+ e.id,
+ e.name,
+ e.category,
+ e.description,
+ e.event_data as "data",
+ e.event_date as "date",
+ e.component_uuid as "componentUuid",
+ e.snapshot_id as "snapshotId",
+ e.created_at as "createdAt"
+ </sql>
+
+ <select id="selectByComponentUuid" parameterType="String" resultType="Event">
+ SELECT
+ <include refid="eventColumns"/>
+ FROM events e
+ <where>
+ AND e.component_uuid=#{uuid}
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="Event" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO events (name, category, description, event_data, event_date, component_uuid, snapshot_id, created_at)
+ VALUES (#{name}, #{category}, #{description}, #{data}, #{date}, #{componentUuid}, #{snapshotId}, #{createdAt})
+ </insert>
+
+ <delete id="delete">
+ DELETE FROM events WHERE id=#{id}
+ </delete>
+
+ <select id="findSnapshotIdOfPreviousVersion" parameterType="map" resultType="long">
+ SELECT s.id
+ FROM snapshots s, events e, projects p
+ <where>
+ AND p.id=#{componentId}
+ AND p.uuid=e.component_uuid
+ AND e.name &lt;&gt; #{currentVersion}
+ AND e.category='Version'
+ AND s.id = e.snapshot_id
+ </where>
+ ORDER BY e.event_date DESC
+ LIMIT 1
+ </select>
+
+ <!-- SQL Server -->
+ <select id="findSnapshotIdOfPreviousVersion" parameterType="map" resultType="long" databaseId="mssql">
+ SELECT TOP 1 s.id
+ FROM snapshots s, events e, projects p
+ <where>
+ AND p.id=#{componentId}
+ AND p.uuid=e.component_uuid
+ AND e.name &lt;&gt; #{currentVersion}
+ AND e.category='Version'
+ AND s.id = e.snapshot_id
+ </where>
+ ORDER BY e.event_date DESC
+ </select>
+
+ <!-- Oracle -->
+ <select id="findSnapshotIdOfPreviousVersion" parameterType="map" resultType="long" databaseId="oracle">
+ SELECT * FROM (SELECT s.id
+ FROM snapshots s, events e, projects p
+ <where>
+ AND p.id=#{componentId}
+ AND p.uuid=e.component_uuid
+ AND e.name &lt;&gt; #{currentVersion}
+ AND e.category='Version'
+ AND s.id = e.snapshot_id
+ </where>
+ ORDER BY e.event_date DESC
+ )
+ WHERE ROWNUM &lt;= 1
+ </select>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanMapper.xml b/sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanMapper.xml
new file mode 100644
index 00000000000..49174ddad74
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanMapper.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.db.issue.ActionPlanMapper">
+
+ <sql id="actionPlanColumns">
+ ap.id,
+ ap.kee as kee,
+ ap.name as name,
+ ap.description as description,
+ ap.user_login as userLogin,
+ ap.project_id as projectId,
+ ap.status as status,
+ ap.deadline as deadLine,
+ ap.created_at as createdAt,
+ ap.updated_at as updatedAt,
+ p.kee as projectKey
+ </sql>
+
+ <insert id="insert" parameterType="ActionPlanIssue" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO action_plans (kee, name, description, user_login, project_id, status, deadline, created_at, updated_at)
+ VALUES (#{kee}, #{name}, #{description}, #{userLogin}, #{projectId}, #{status}, #{deadLine}, #{createdAt},
+ #{updatedAt})
+ </insert>
+
+ <update id="update" parameterType="ActionPlanIssue">
+ update action_plans set
+ name=#{name},
+ description=#{description},
+ user_login=#{userLogin},
+ project_id=#{projectId},
+ status=#{status},
+ deadline=#{deadLine},
+ updated_at=current_timestamp
+ where kee = #{kee}
+ </update>
+
+ <delete id="delete" parameterType="String">
+ delete from action_plans where kee=#{key}
+ </delete>
+
+ <select id="findByKey" parameterType="long" resultType="ActionPlanIssue">
+ select
+ <include refid="actionPlanColumns"/>
+ from action_plans ap, projects p
+ <where>
+ and ap.kee=#{key}
+ and ap.project_id=p.id
+ </where>
+ </select>
+
+ <select id="findByKeys" parameterType="long" resultType="ActionPlanIssue">
+ select
+ <include refid="actionPlanColumns"/>
+ from action_plans ap, projects p
+ <where>
+ and ap.kee in
+ <foreach collection="keys" open="(" close=")" item="key" separator=",">
+ #{key}
+ </foreach>
+ and ap.project_id=p.id
+ </where>
+ </select>
+
+ <select id="findOpenByProjectId" parameterType="long" resultType="ActionPlanIssue">
+ select
+ <include refid="actionPlanColumns"/>
+ from action_plans ap, projects p
+ <where>
+ and ap.project_id=#{projectId}
+ and ap.status='OPEN'
+ and ap.project_id=p.id
+ </where>
+ </select>
+
+ <select id="findByNameAndProjectId" parameterType="long" resultType="ActionPlanIssue">
+ select
+ <include refid="actionPlanColumns"/>
+ from action_plans ap, projects p
+ <where>
+ and ap.project_id=#{projectId}
+ and ap.name=#{name}
+ and ap.project_id=p.id
+ </where>
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanStatsMapper.xml b/sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanStatsMapper.xml
new file mode 100644
index 00000000000..ec6ddb40ea3
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/issue/ActionPlanStatsMapper.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.db.issue.ActionPlanStatsMapper">
+
+ <sql id="actionPlanColumns">
+ ap.id as id,
+ ap.kee as kee,
+ ap.name as name,
+ ap.description as description,
+ ap.user_login as userLogin,
+ ap.project_id as projectId,
+ ap.status as status,
+ ap.deadline as deadLine,
+ ap.created_at as createdAt,
+ ap.updated_at as updatedAt,
+ p.kee as projectKey
+ </sql>
+
+ <select id="findByProjectId" parameterType="map" resultType="ActionPlanStats">
+ select<include refid="actionPlanColumns"/>, count(total_issues.id) as totalIssues, count(open_issues.id) as
+ unresolvedIssues
+ from action_plans ap
+ left join projects p on p.id = ap.project_id
+ left join issues total_issues on total_issues.action_plan_key = ap.kee
+ left join issues open_issues on open_issues.id = total_issues.id and open_issues.resolution is null
+ <where>
+ and ap.project_id = #{projectId}
+ </where>
+ group by ap.id, ap.kee, ap.name, ap.description, ap.user_login, ap.project_id, ap.status, ap.deadline,
+ ap.created_at, ap.updated_at, p.kee
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/issue/IssueChangeMapper.xml b/sonar-db/src/main/resources/org/sonar/db/issue/IssueChangeMapper.xml
new file mode 100644
index 00000000000..96caa141092
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/issue/IssueChangeMapper.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.issue.IssueChangeMapper">
+
+ <sql id="issueChangeColumns">
+ c.id,
+ c.kee as kee,
+ c.issue_key as issueKey,
+ c.user_login as userLogin,
+ c.change_type as changeType,
+ c.change_data as changeData,
+ c.created_at as createdAt,
+ c.updated_at as updatedAt,
+ c.issue_change_creation_date as issueChangeCreationDate
+ </sql>
+
+ <insert id="insert" parameterType="IssueChange" useGeneratedKeys="false" keyProperty="id">
+ INSERT INTO issue_changes (kee, issue_key, user_login, change_type, change_data, created_at, updated_at,
+ issue_change_creation_date)
+ VALUES (#{kee,jdbcType=VARCHAR}, #{issueKey,jdbcType=VARCHAR}, #{userLogin,jdbcType=VARCHAR},
+ #{changeType,jdbcType=VARCHAR}, #{changeData,jdbcType=VARCHAR}, #{createdAt,jdbcType=BIGINT},
+ #{updatedAt,jdbcType=BIGINT}, #{issueChangeCreationDate,jdbcType=BIGINT})
+ </insert>
+
+ <delete id="delete" parameterType="string">
+ delete from issue_changes where kee=#{id}
+ </delete>
+
+ <update id="update" parameterType="map">
+ update issue_changes set change_data=#{changeData}, updated_at=#{updatedAt} where kee=#{kee}
+ </update>
+
+ <select id="selectByIssuesAndType" parameterType="map" resultType="IssueChange">
+ select
+ <include refid="issueChangeColumns"/>
+ from issue_changes c
+ where c.change_type=#{changeType} and c.issue_key in
+ <foreach collection="issueKeys" open="(" close=")" item="key" separator=",">
+ #{key}
+ </foreach>
+ order by c.created_at
+ </select>
+
+ <select id="selectByKeyAndType" parameterType="map" resultType="IssueChange">
+ select
+ <include refid="issueChangeColumns"/>
+ from issue_changes c
+ where c.change_type=#{changeType} and c.kee=#{key}
+ </select>
+
+ <select id="selectByIssue" parameterType="string" resultType="IssueChange">
+ select
+ <include refid="issueChangeColumns"/>
+ from issue_changes c
+ where c.issue_key=#{id}
+ order by created_at asc
+ </select>
+
+ <select id="selectChangelogOfNonClosedIssuesByComponent" parameterType="map" resultType="IssueChange">
+ select
+ <include refid="issueChangeColumns"/>
+ from issue_changes c
+ inner join issues i on i.kee = c.issue_key
+ where i.component_uuid=#{componentUuid}
+ and c.change_type=#{changeType}
+ and i.status &lt;&gt; 'CLOSED'
+ </select>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterFavouriteMapper.xml b/sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterFavouriteMapper.xml
new file mode 100644
index 00000000000..77db11f635c
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterFavouriteMapper.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.issue.IssueFilterFavouriteMapper">
+
+ <sql id="issueFilterFavouriteColumns">
+ filter_favourites.id as id,
+ filter_favourites.user_login as userLogin,
+ filter_favourites.issue_filter_id as issueFilterId,
+ filter_favourites.created_at as createdAt
+ </sql>
+
+ <select id="selectById" parameterType="long" resultType="issueFilterFavourite">
+ select
+ <include refid="issueFilterFavouriteColumns"/>
+ from issue_filter_favourites filter_favourites
+ where filter_favourites.id=#{id}
+ </select>
+
+ <select id="selectByFilterId" parameterType="long" resultType="issueFilterFavourite">
+ select
+ <include refid="issueFilterFavouriteColumns"/>
+ from issue_filter_favourites filter_favourites
+ where filter_favourites.issue_filter_id=#{filterId}
+ </select>
+
+ <insert id="insert" parameterType="issueFilterFavourite" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO issue_filter_favourites (user_login, issue_filter_id, created_at)
+ VALUES (#{userLogin}, #{issueFilterId}, current_timestamp)
+ </insert>
+
+ <delete id="delete" parameterType="long">
+ delete from issue_filter_favourites where id=#{id}
+ </delete>
+
+ <delete id="deleteByFilterId" parameterType="long">
+ delete from issue_filter_favourites where issue_filter_id=#{issueFilterId}
+ </delete>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterMapper.xml b/sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterMapper.xml
new file mode 100644
index 00000000000..aca8dffc6c2
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/issue/IssueFilterMapper.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.issue.IssueFilterMapper">
+
+ <sql id="issueFilterColumns">
+ filters.id as id,
+ filters.name as name,
+ filters.user_login as userLogin,
+ filters.shared as shared,
+ filters.description as description,
+ filters.data as data,
+ filters.created_at as createdAt,
+ filters.updated_at as updatedAt
+ </sql>
+
+ <select id="selectById" parameterType="long" resultType="IssueFilter">
+ select
+ <include refid="issueFilterColumns"/>
+ from issue_filters filters
+ where filters.id=#{id}
+ </select>
+
+ <select id="selectByUser" parameterType="String" resultType="IssueFilter">
+ select
+ <include refid="issueFilterColumns"/>
+ from issue_filters filters
+ where filters.user_login=#{user}
+ </select>
+
+ <select id="selectFavoriteFiltersByUser" parameterType="String" resultType="IssueFilter">
+ select
+ <include refid="issueFilterColumns"/>
+ from issue_filters filters
+ inner join issue_filter_favourites fav on fav.issue_filter_id = filters.id
+ where fav.user_login=#{user}
+ </select>
+
+ <select id="selectSharedFilters" parameterType="String" resultType="IssueFilter">
+ select
+ <include refid="issueFilterColumns"/>
+ from issue_filters filters
+ where filters.shared=${_true}
+ </select>
+
+ <select id="selectProvidedFilterByName" parameterType="String" resultType="IssueFilter">
+ select
+ <include refid="issueFilterColumns"/>
+ from issue_filters filters
+ where filters.user_login is null
+ and filters.shared=${_true}
+ and filters.name=#{name}
+ </select>
+
+ <insert id="insert" parameterType="IssueFilter" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO issue_filters (name, user_login, shared, description, data, created_at, updated_at)
+ VALUES (#{name}, #{userLogin}, #{shared}, #{description}, #{data}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <update id="update" parameterType="IssueFilter">
+ update issue_filters set
+ name=#{name},
+ shared=#{shared},
+ description=#{description},
+ data=#{data},
+ user_login=#{userLogin},
+ updated_at=current_timestamp
+ where id=#{id}
+ </update>
+
+ <delete id="delete" parameterType="long">
+ delete from issue_filters where id=#{id}
+ </delete>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/sonar-db/src/main/resources/org/sonar/db/issue/IssueMapper.xml
new file mode 100644
index 00000000000..790040bc6e3
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/issue/IssueMapper.xml
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.db.issue.IssueMapper">
+
+ <sql id="issueColumns">
+ i.id,
+ i.kee as kee,
+ i.rule_id as ruleId,
+ i.action_plan_key as actionPlanKey,
+ i.severity as severity,
+ i.manual_severity as manualSeverity,
+ i.message as message,
+ i.line as line,
+ i.effort_to_fix as effortToFix,
+ i.technical_debt as debt,
+ i.status as status,
+ i.resolution as resolution,
+ i.checksum as checksum,
+ i.reporter as reporter,
+ i.assignee as assignee,
+ i.author_login as authorLogin,
+ i.tags as tagsString,
+ i.issue_attributes as issueAttributes,
+ i.issue_creation_date as issueCreationTime,
+ i.issue_update_date as issueUpdateTime,
+ i.issue_close_date as issueCloseTime,
+ i.created_at as createdAt,
+ i.updated_at as updatedAt,
+ r.plugin_rule_key as ruleKey,
+ r.plugin_name as ruleRepo,
+ r.language as language,
+ p.kee as componentKey,
+ i.component_uuid as componentUuid,
+ p.module_uuid as moduleUuid,
+ p.module_uuid_path as moduleUuidPath,
+ p.path as filePath,
+ root.kee as projectKey,
+ i.project_uuid as projectUuid
+ </sql>
+
+ <sql id="sortColumn">
+ <if test="query.sort() != null">,
+ <choose>
+ <when test="'SEVERITY'.equals(query.sort())">
+ i.severity as severity
+ </when>
+ <when test="'STATUS'.equals(query.sort())">
+ i.status as status
+ </when>
+ <when test="'ASSIGNEE'.equals(query.sort())">
+ i.assignee as assignee
+ </when>
+ <when test="'CREATION_DATE'.equals(query.sort())">
+ i.issue_creation_date as issueCreationTime
+ </when>
+ <when test="'UPDATE_DATE'.equals(query.sort())">
+ i.issue_update_date as issueUpdateTime
+ </when>
+ <when test="'CLOSE_DATE'.equals(query.sort())">
+ i.issue_close_date as issueCloseTime
+ </when>
+ </choose>
+ </if>
+ </sql>
+
+ <insert id="insert" parameterType="Issue" useGeneratedKeys="false" keyProperty="id">
+ INSERT INTO issues (kee, rule_id, action_plan_key, severity, manual_severity,
+ message, line, effort_to_fix, technical_debt, status, tags,
+ resolution, checksum, reporter, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date,
+ issue_close_date, created_at, updated_at, component_uuid, project_uuid)
+ VALUES (#{kee,jdbcType=VARCHAR}, #{ruleId,jdbcType=INTEGER}, #{actionPlanKey,jdbcType=VARCHAR},
+ #{severity,jdbcType=VARCHAR},
+ #{manualSeverity,jdbcType=BOOLEAN}, #{message,jdbcType=VARCHAR}, #{line,jdbcType=INTEGER},
+ #{effortToFix,jdbcType=DOUBLE}, #{debt,jdbcType=INTEGER}, #{status,jdbcType=VARCHAR},
+ #{tagsString,jdbcType=VARCHAR}, #{resolution,jdbcType=VARCHAR}, #{checksum,jdbcType=VARCHAR},
+ #{reporter,jdbcType=VARCHAR}, #{assignee,jdbcType=VARCHAR}, #{authorLogin,jdbcType=VARCHAR},
+ #{issueAttributes,jdbcType=VARCHAR},
+ #{issueCreationTime,jdbcType=BIGINT},#{issueUpdateTime,jdbcType=BIGINT}, #{issueCloseTime,jdbcType=BIGINT},
+ #{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT},
+ #{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR})
+ </insert>
+
+ <!--
+ IMPORTANT - invariant columns can't be updated. See IssueDto#toDtoForUpdate()
+ -->
+ <update id="update" parameterType="Issue">
+ update issues set
+ action_plan_key=#{actionPlanKey,jdbcType=VARCHAR},
+ severity=#{severity,jdbcType=VARCHAR},
+ manual_severity=#{manualSeverity,jdbcType=BOOLEAN},
+ message=#{message,jdbcType=VARCHAR},
+ line=#{line,jdbcType=INTEGER},
+ effort_to_fix=#{effortToFix,jdbcType=DOUBLE},
+ technical_debt=#{debt,jdbcType=INTEGER},
+ status=#{status,jdbcType=VARCHAR},
+ resolution=#{resolution,jdbcType=VARCHAR},
+ checksum=#{checksum,jdbcType=VARCHAR},
+ reporter=#{reporter,jdbcType=VARCHAR},
+ assignee=#{assignee,jdbcType=VARCHAR},
+ author_login=#{authorLogin,jdbcType=VARCHAR},
+ tags=#{tagsString,jdbcType=VARCHAR},
+ project_uuid=#{projectUuid,jdbcType=VARCHAR},
+ issue_attributes=#{issueAttributes,jdbcType=VARCHAR},
+ issue_creation_date=#{issueCreationTime,jdbcType=BIGINT},
+ issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
+ issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
+ updated_at=#{updatedAt,jdbcType=BIGINT}
+ where kee = #{kee}
+ </update>
+
+ <!--
+ IMPORTANT - invariant columns can't be updated. See IssueDto#toDtoForUpdate()
+ -->
+ <update id="updateIfBeforeSelectedDate" parameterType="Issue">
+ update issues set
+ action_plan_key=#{actionPlanKey,jdbcType=VARCHAR},
+ severity=#{severity,jdbcType=VARCHAR},
+ manual_severity=#{manualSeverity,jdbcType=BOOLEAN},
+ message=#{message,jdbcType=VARCHAR},
+ line=#{line,jdbcType=INTEGER},
+ effort_to_fix=#{effortToFix,jdbcType=DOUBLE},
+ technical_debt=#{debt,jdbcType=INTEGER},
+ status=#{status,jdbcType=VARCHAR},
+ resolution=#{resolution,jdbcType=VARCHAR},
+ checksum=#{checksum,jdbcType=VARCHAR},
+ reporter=#{reporter,jdbcType=VARCHAR},
+ assignee=#{assignee,jdbcType=VARCHAR},
+ author_login=#{authorLogin,jdbcType=VARCHAR},
+ tags=#{tagsString,jdbcType=VARCHAR},
+ project_uuid=#{projectUuid,jdbcType=VARCHAR},
+ issue_attributes=#{issueAttributes,jdbcType=VARCHAR},
+ issue_creation_date=#{issueCreationTime,jdbcType=BIGINT},
+ issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
+ issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
+ updated_at=#{updatedAt,jdbcType=BIGINT}
+ where kee = #{kee} and updated_at &lt;= #{selectedAt}
+ </update>
+
+ <select id="selectByKey" parameterType="String" resultType="Issue">
+ select
+ <include refid="issueColumns"/>
+ from issues i
+ inner join rules r on r.id=i.rule_id
+ inner join projects p on p.uuid=i.component_uuid
+ inner join projects root on root.uuid=i.project_uuid
+ where i.kee=#{kee}
+ </select>
+
+ <select id="selectNonClosedByComponentUuid" parameterType="String" resultType="Issue">
+ select
+ <include refid="issueColumns"/>
+ from issues i
+ inner join rules r on r.id=i.rule_id
+ inner join projects p on p.uuid=i.component_uuid
+ inner join projects root on root.uuid=i.project_uuid
+ where
+ i.component_uuid=#{componentUuid} and
+ i.status &lt;&gt; 'CLOSED'
+ </select>
+
+ <select id="selectNonClosedIssuesByModule" parameterType="long" resultType="Issue">
+ select
+ i.id,
+ i.kee as kee,
+ i.rule_id as ruleId,
+ i.component_uuid as componentUuid,
+ i.project_uuid as projectUuid,
+ i.action_plan_key as actionPlanKey,
+ i.severity as severity,
+ i.manual_severity as manualSeverity,
+ i.message as message,
+ i.line as line,
+ i.effort_to_fix as effortToFix,
+ i.technical_debt as debt,
+ i.status as status,
+ i.resolution as resolution,
+ i.checksum as checksum,
+ i.reporter as reporter,
+ i.assignee as assignee,
+ i.author_login as authorLogin,
+ i.tags as tagsString,
+ i.issue_attributes as issueAttributes,
+ i.issue_creation_date as issueCreationTime,
+ i.issue_update_date as issueUpdateTime,
+ i.issue_close_date as issueCloseTime,
+ i.created_at as createdAt,
+ i.updated_at as updatedAt,
+ r.plugin_rule_key as ruleKey,
+ r.plugin_name as ruleRepo,
+ p.kee as componentKey,
+ root.kee as projectKey
+ from issues i
+ inner join (select p.id, p.uuid,p.kee from projects p where (p.root_id=#{id} and p.qualifier &lt;&gt; 'BRC') or
+ (p.id=#{id})) p on p.uuid=i.component_uuid
+ inner join rules r on r.id=i.rule_id
+ left outer join projects root on root.uuid=i.project_uuid
+ where i.status &lt;&gt; 'CLOSED'
+ </select>
+
+ <select id="selectComponentUuidsOfOpenIssuesForProjectUuid" parameterType="string" resultType="string">
+ select distinct(i.component_uuid)
+ from issues i
+ where i.project_uuid=#{projectUuid} and i.status &lt;&gt; 'CLOSED'
+ </select>
+
+ <select id="selectByKeys" parameterType="map" resultType="Issue">
+ select
+ <include refid="issueColumns"/>
+ from issues i
+ inner join rules r on r.id=i.rule_id
+ inner join projects p on p.uuid=i.component_uuid
+ inner join projects root on root.uuid=i.project_uuid
+ where i.kee in
+ <foreach collection="list" open="(" close=")" item="key" separator=",">
+ #{key}
+ </foreach>
+ </select>
+
+ <select id="selectByActionPlan" parameterType="map" resultType="Issue">
+ select
+ <include refid="issueColumns"/>
+ from issues i
+ inner join rules r on r.id=i.rule_id
+ inner join projects p on p.uuid=i.component_uuid
+ inner join projects root on root.uuid=i.project_uuid
+ <where>
+ and i.action_plan_key=#{action_plan}
+ </where>
+ </select>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/loadedtemplate/LoadedTemplateMapper.xml b/sonar-db/src/main/resources/org/sonar/db/loadedtemplate/LoadedTemplateMapper.xml
new file mode 100644
index 00000000000..0395aad517a
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/loadedtemplate/LoadedTemplateMapper.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.loadedtemplate.LoadedTemplateMapper">
+
+ <resultMap id="loadedTemplateResultMap" type="LoadedTemplate">
+ <result property="id" column="id"/>
+ <result property="key" column="kee"/>
+ <result property="type" column="template_type"/>
+ </resultMap>
+
+ <select id="countByTypeAndKey" parameterType="map" resultType="int">
+ SELECT count(*)
+ FROM loaded_templates
+ WHERE kee = #{key} AND template_type = #{type}
+ </select>
+
+ <insert id="insert" parameterType="LoadedTemplate" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO loaded_templates (kee, template_type)
+ VALUES (#{key}, #{type})
+ </insert>
+
+ <delete id="delete" parameterType="map">
+ delete from loaded_templates where kee = #{key} AND template_type = #{type}
+ </delete>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/measure/CustomMeasureMapper.xml b/sonar-db/src/main/resources/org/sonar/db/measure/CustomMeasureMapper.xml
new file mode 100644
index 00000000000..119cec178b4
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/measure/CustomMeasureMapper.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.measure.CustomMeasureMapper">
+ <sql id="selectColumns">
+ m.id,
+ m.metric_id as metricId,
+ m.component_uuid as componentUuid,
+ m.value,
+ m.text_value as textValue,
+ m.user_login as userLogin,
+ m.description,
+ m.created_at as createdAt,
+ m.updated_at as updatedAt
+ </sql>
+
+ <select id="selectById" resultType="CustomMeasure">
+ select
+ <include refid="selectColumns"/>
+ from manual_measures m
+ where m.id=#{id}
+ </select>
+
+ <select id="selectByMetricId" resultType="CustomMeasure">
+ select
+ <include refid="selectColumns"/>
+ from manual_measures m
+ where m.metric_id=#{metricId}
+ </select>
+
+ <select id="selectByComponentUuid" resultType="CustomMeasure">
+ select
+ <include refid="selectColumns"/>
+ from manual_measures m
+ where m.component_uuid=#{componentUuid}
+ </select>
+
+ <insert id="insert" parameterType="CustomMeasure" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
+ INSERT INTO manual_measures (
+ metric_id, component_uuid, value, text_value, user_login, description, created_at, updated_at
+ )
+ VALUES (
+ #{metricId, jdbcType=INTEGER}, #{componentUuid, jdbcType=VARCHAR},
+ #{value, jdbcType=DOUBLE}, #{textValue, jdbcType=VARCHAR}, #{userLogin, jdbcType=VARCHAR},
+ #{description, jdbcType=VARCHAR}, #{createdAt, jdbcType=BIGINT}, #{updatedAt, jdbcType=BIGINT}
+ )
+ </insert>
+
+ <update id="update" parameterType="CustomMeasure">
+ update manual_measures
+ set value = #{value, jdbcType=DOUBLE},
+ text_value = #{textValue, jdbcType=VARCHAR},
+ description = #{description, jdbcType=VARCHAR},
+ user_login = #{userLogin, jdbcType=VARCHAR},
+ updated_at = #{updatedAt, jdbcType=BIGINT}
+ where id = #{id}
+ </update>
+
+ <delete id="deleteByMetricIds">
+ delete from manual_measures
+ where metric_id in
+ <foreach collection="metricIds" item="metricId" open="(" close=")" separator=",">
+ #{metricId}
+ </foreach>
+ </delete>
+
+ <delete id="delete">
+ delete from manual_measures
+ where id=#{id}
+ </delete>
+
+ <select id="countByComponentUuid" resultType="Integer">
+ select count(*)
+ from manual_measures m
+ where m.component_uuid=#{componentUuid}
+ </select>
+
+ <select id="countByComponentIdAndMetricId" resultType="Integer">
+ select count(*)
+ from manual_measures m
+ where m.metric_id=#{metricId} and m.component_uuid=#{componentUuid}
+ </select>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/measure/MeasureFilterMapper.xml b/sonar-db/src/main/resources/org/sonar/db/measure/MeasureFilterMapper.xml
new file mode 100644
index 00000000000..85e85b3fa4e
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/measure/MeasureFilterMapper.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.measure.MeasureFilterMapper">
+
+ <select id="findSystemFilterByName" parameterType="string" resultType="MeasureFilter">
+ select id, name, user_id as "userId", shared, description, data, created_at as "createdAt", updated_at as
+ "updatedAt"
+ from measure_filters WHERE user_id is null and name=#{id}
+ </select>
+
+ <insert id="insert" parameterType="MeasureFilter" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO measure_filters (name, user_id, shared, description, data, created_at, updated_at)
+ VALUES (#{name}, #{userId}, #{shared}, #{description}, #{data}, #{createdAt}, #{updatedAt})
+ </insert>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml b/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml
new file mode 100644
index 00000000000..7d83e993e05
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.measure.MeasureMapper">
+
+ <sql id="measureColumns">
+ pm.id,
+ pm.snapshot_id as snapshotId,
+ pm.value as value,
+ pm.text_value as textValue,
+ pm.alert_status as alertStatus,
+ pm.alert_text as alertText,
+ pm.measure_data as dataValue,
+ pm.variation_value_1 as variation1,
+ pm.variation_value_2 as variation2,
+ pm.variation_value_3 as variation3,
+ pm.variation_value_4 as variation4,
+ pm.variation_value_5 as variation5,
+ p.kee as componentKey,
+ metric.name as metricKey
+ </sql>
+
+ <select id="selectByComponentAndMetric" parameterType="map" resultType="Measure">
+ SELECT metric.name as metric_name,
+ <include refid="measureColumns"/>
+ FROM project_measures pm
+ INNER JOIN snapshots s ON s.id=pm.snapshot_id AND s.islast=${_true}
+ INNER JOIN projects p ON p.id=s.project_id AND p.enabled=${_true}
+ INNER JOIN metrics metric ON metric.id=pm.metric_id
+ <where>
+ AND p.kee = #{componentKey}
+ AND metric.name=#{metricKey}
+ AND pm.rule_id IS NULL
+ AND pm.characteristic_id IS NULL
+ AND pm.person_id IS NULL
+ </where>
+ </select>
+
+ <select id="selectByComponentAndMetrics" parameterType="map" resultType="Measure">
+ SELECT metric.name as metric_name,
+ <include refid="measureColumns"/>
+ FROM project_measures pm
+ INNER JOIN snapshots s ON s.id=pm.snapshot_id AND s.islast=${_true}
+ INNER JOIN projects p ON p.id=s.project_id AND p.enabled=${_true}
+ INNER JOIN metrics metric ON metric.id=pm.metric_id
+ <where>
+ AND p.kee = #{componentKey}
+ AND
+ <foreach item="metricKey" index="index" collection="metricKeys" open="(" separator=" or " close=")">
+ metric.name=#{metricKey}
+ </foreach>
+ AND pm.rule_id IS NULL
+ AND pm.characteristic_id IS NULL
+ AND pm.person_id IS NULL
+ </where>
+ </select>
+
+ <select id="countByComponentAndMetric" parameterType="map" resultType="long">
+ SELECT count(pm.id)
+ FROM project_measures pm
+ INNER JOIN snapshots s ON s.id=pm.snapshot_id AND s.islast=${_true}
+ INNER JOIN metrics metric ON metric.id=pm.metric_id
+ INNER JOIN projects p ON p.id=s.project_id AND p.enabled=${_true}
+ <where>
+ AND p.kee = #{componentKey}
+ AND metric.name = #{metricKey}
+ AND pm.rule_id IS NULL
+ AND pm.characteristic_id IS NULL
+ AND pm.person_id IS NULL
+ </where>
+ </select>
+
+ <select id="selectByComponentUuidAndProjectSnapshotIdAndStatusAndMetricIds" parameterType="map"
+ resultType="org.sonar.db.measure.PastMeasureDto">
+ SELECT pm.id as id, pm.metric_id as metricId, pm.rule_id as ruleId, pm.characteristic_id as characteristicId,
+ pm.person_id as personId, pm.value as value
+ FROM project_measures pm
+ INNER JOIN snapshots s ON s.id=pm.snapshot_id AND s.status=#{status}
+ INNER JOIN projects p ON p.id=s.project_id AND p.enabled=${_true}
+ <where>
+ AND p.uuid = #{componentUuid}
+ AND (s.root_snapshot_id=#{rootSnapshotId} OR s.id=#{rootSnapshotId})
+ AND
+ <foreach item="metricId" index="index" collection="metricIds" open="(" separator=" or " close=")">
+ pm.metric_id=#{metricId}
+ </foreach>
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="Measure" useGeneratedKeys="false">
+ INSERT INTO project_measures (
+ value, metric_id, snapshot_id, rule_id, text_value, project_id, alert_status, alert_text, description,
+ characteristic_id, person_id, variation_value_1, variation_value_2, variation_value_3, variation_value_4,
+ variation_value_5, measure_data)
+ VALUES (
+ #{value, jdbcType=DOUBLE}, #{metricId, jdbcType=INTEGER}, #{snapshotId, jdbcType=INTEGER},
+ #{ruleId, jdbcType=INTEGER}, #{textValue, jdbcType=VARCHAR},
+ #{componentId, jdbcType=INTEGER}, #{alertStatus, jdbcType=VARCHAR}, #{alertText, jdbcType=VARCHAR},
+ #{description, jdbcType=VARCHAR}, #{characteristicId, jdbcType=INTEGER},
+ #{personId, jdbcType=INTEGER}, #{variation1, jdbcType=DOUBLE}, #{variation2, jdbcType=DOUBLE},
+ #{variation3, jdbcType=DOUBLE},
+ #{variation4, jdbcType=DOUBLE}, #{variation5, jdbcType=DOUBLE}, #{dataValue, jdbcType=BINARY}
+ )
+ </insert>
+
+ <select id="selectMetricKeysForSnapshot" parameterType="long" resultType="string">
+ SELECT DISTINCT m.name
+ FROM project_measures pm
+ INNER JOIN metrics m ON m.id=pm.metric_id
+ WHERE pm.snapshot_id=#{snapshotId}
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/metric/MetricMapper.xml b/sonar-db/src/main/resources/org/sonar/db/metric/MetricMapper.xml
new file mode 100644
index 00000000000..ca4a79a3a82
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/metric/MetricMapper.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.metric.MetricMapper">
+
+ <sql id="metricColumns">
+ m.id,
+ m.name as kee,
+ m.description,
+ m.direction,
+ m.domain,
+ m.short_name as shortName,
+ m.qualitative,
+ m.val_type as valueType,
+ m.user_managed as userManaged,
+ m.enabled,
+ m.worst_value as worstValue,
+ m.best_value as bestValue,
+ m.optimized_best_value as optimizedBestValue,
+ m.hidden,
+ m.delete_historical_data as deleteHistoricalData
+ </sql>
+
+ <select id="selectByKey" parameterType="map" resultType="org.sonar.db.metric.MetricDto">
+ SELECT
+ <include refid="metricColumns"/>
+ FROM metrics m
+ <where>
+ AND m.name=#{key}
+ </where>
+ </select>
+
+ <select id="selectAllEnabled" parameterType="map" resultType="org.sonar.db.metric.MetricDto">
+ SELECT
+ <include refid="metricColumns"/>
+ FROM metrics m
+ <where>
+ AND m.enabled=${_true}
+ <if test="isCustom!=null">
+ <if test="isCustom.equals(true)">
+ AND m.user_managed=${_true}
+ </if>
+ <if test="isCustom.equals(false)">
+ AND m.user_managed=${_false}
+ </if>
+ </if>
+ </where>
+ ORDER BY UPPER(m.short_name), m.short_name
+ </select>
+
+ <select id="selectAvailableCustomMetricsByComponentUuid" resultType="org.sonar.db.metric.MetricDto">
+ select
+ <include refid="metricColumns"/>
+ from metrics m
+ left join manual_measures mm on mm.metric_id = m.id and mm.component_uuid=#{componentUuid}
+ where m.enabled=${_true}
+ and m.user_managed=${_true}
+ and mm.id is null
+ ORDER BY UPPER(m.short_name), m.short_name
+ </select>
+
+ <select id="countEnabled" resultType="Integer">
+ SELECT COUNT(*)
+ FROM metrics m
+ <where>
+ AND m.enabled=${_true}
+ <if test="isCustom!=null">
+ <if test="isCustom.equals(true)">
+ AND m.user_managed=${_true}
+ </if>
+ <if test="isCustom.equals(false)">
+ AND m.user_managed=${_false}
+ </if>
+ </if>
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="org.sonar.db.metric.MetricDto" useGeneratedKeys="true" keyColumn="id"
+ keyProperty="id">
+ INSERT INTO metrics (
+ name, description, direction, domain, short_name, qualitative, val_type, user_managed, enabled, worst_value,
+ best_value, optimized_best_value, hidden, delete_historical_data)
+ VALUES (
+ #{kee, jdbcType=VARCHAR}, #{description, jdbcType=VARCHAR}, #{direction, jdbcType=INTEGER},
+ #{domain, jdbcType=VARCHAR}, #{shortName, jdbcType=VARCHAR}, #{qualitative, jdbcType=BOOLEAN},
+ #{valueType, jdbcType=VARCHAR}, #{userManaged, jdbcType=BOOLEAN}, #{enabled, jdbcType=BOOLEAN},
+ #{worstValue, jdbcType=DOUBLE}, #{bestValue, jdbcType=DOUBLE},
+ #{optimizedBestValue, jdbcType=BOOLEAN}, #{hidden, jdbcType=BOOLEAN}, #{deleteHistoricalData, jdbcType=BOOLEAN}
+ )
+ </insert>
+
+ <update id="update" parameterType="org.sonar.db.metric.MetricDto">
+ update metrics
+ set
+ name=#{key, jdbcType=VARCHAR},
+ short_name=#{shortName, jdbcType=VARCHAR},
+ val_type=#{valueType, jdbcType=VARCHAR},
+ enabled=#{enabled, jdbcType=BOOLEAN},
+ domain=#{domain, jdbcType=VARCHAR},
+ description=#{description, jdbcType=VARCHAR},
+ direction=#{direction, jdbcType=INTEGER},
+ hidden=#{hidden, jdbcType=BOOLEAN},
+ qualitative=#{qualitative, jdbcType=BOOLEAN}
+ where id=#{id}
+ </update>
+
+ <select id="selectDomains" resultType="String">
+ select distinct domain
+ from metrics m
+ where m.domain is not null and m.enabled=${_true}
+ </select>
+
+ <update id="disableByIds">
+ update metrics
+ set enabled=${_false}
+ <where>
+ AND user_managed=${_true}
+ AND id in
+ <foreach item="id" collection="ids" open="(" separator="," close=")">
+ #{id}
+ </foreach>
+ </where>
+ </update>
+
+ <update id="disableByKey" parameterType="string">
+ update metrics
+ set enabled=${_false}
+ where name=#{key}
+ </update>
+
+ <select id="selectByKeys" resultType="org.sonar.db.metric.MetricDto">
+ SELECT
+ <include refid="metricColumns"/>
+ FROM metrics m
+ <where>
+ AND m.name in
+ <foreach item="key" collection="keys" open="(" separator="," close=")">
+ #{key}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectByIds" resultType="org.sonar.db.metric.MetricDto">
+ SELECT
+ <include refid="metricColumns"/>
+ FROM metrics m
+ <where>
+ AND m.id in
+ <foreach item="id" collection="ids" open="(" separator="," close=")">
+ #{id}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectById" resultType="org.sonar.db.metric.MetricDto">
+ SELECT
+ <include refid="metricColumns"/>
+ FROM metrics m
+ <where>
+ AND m.id=#{id}
+ </where>
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/notification/NotificationQueueMapper.xml b/sonar-db/src/main/resources/org/sonar/db/notification/NotificationQueueMapper.xml
new file mode 100644
index 00000000000..c09eafc31d0
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/notification/NotificationQueueMapper.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.db.notification.NotificationQueueMapper">
+
+ <insert id="insert" parameterType="NotificationQueue" useGeneratedKeys="false">
+ INSERT INTO notifications (data)
+ VALUES (#{data})
+ </insert>
+
+ <delete id="delete" parameterType="long">
+ delete from notifications where id=#{id}
+ </delete>
+
+ <select id="count" resultType="long">
+ select count(*) from notifications
+ </select>
+
+ <select id="findOldest" parameterType="int" resultType="NotificationQueue">
+ select id, data
+ from notifications
+ order by id asc
+ limit #{count}
+ </select>
+
+ <!-- SQL Server -->
+ <select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="mssql">
+ select top (#{count}) id, data
+ from notifications
+ order by id asc
+ </select>
+
+ <!-- Oracle -->
+ <select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="oracle">
+ select * from (select
+ id, data
+ from notifications
+ order by id asc
+ )
+ where rownum &lt;= #{count}
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml b/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml
new file mode 100644
index 00000000000..79a705a44dc
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.permission.PermissionMapper">
+
+ <select id="selectUsers" parameterType="map" resultType="UserWithPermission">
+ SELECT u.login as login, u.name as name, user_role.role as permission
+ FROM users u
+ LEFT JOIN user_roles user_role ON user_role.user_id=u.id
+ AND user_role.role=#{query.permission}
+ <if test="componentId != null">
+ AND user_role.resource_id=#{componentId}
+ </if>
+ <if test="componentId == null">
+ AND user_role.resource_id IS NULL
+ </if>
+ <where>
+ u.active = ${_true}
+ <choose>
+ <when test="query.membership() == 'IN'">
+ AND user_role.role IS NOT NULL
+ </when>
+ <when test="query.membership() == 'OUT'">
+ AND user_role.role IS NULL
+ </when>
+ </choose>
+ <if test="query.search() != null">
+ AND (UPPER(u.name) LIKE #{query.searchSql} ESCAPE '/')
+ </if>
+ </where>
+ ORDER BY u.name
+ </select>
+
+ <select id="selectGroups" parameterType="map" resultType="GroupWithPermission">
+ SELECT name, description, permission FROM
+ (SELECT g.name as name, g.description as description, group_role.role as permission
+ FROM groups g
+ LEFT JOIN group_roles group_role ON group_role.group_id=g.id
+ AND group_role.role=#{query.permission}
+ <if test="componentId != null">
+ AND group_role.resource_id=#{componentId}
+ </if>
+ <if test="componentId == null">
+ AND group_role.resource_id IS NULL
+ </if>
+ UNION
+ -- Add Anyone group permission
+ SELECT #{anyoneGroup} as name, NULL as description, group_role.role as permission
+ FROM group_roles group_role
+ <where>
+ AND group_role.role=#{query.permission}
+ AND group_role.group_id IS NULL
+ <if test="componentId != null">
+ AND group_role.resource_id=#{componentId}
+ </if>
+ <if test="componentId == null">
+ AND group_role.resource_id IS NULL
+ </if>
+ </where>
+ ) groups
+ <where>
+ <if test="query.search() != null">
+ AND (UPPER(groups.name) LIKE #{query.searchSql} ESCAPE '/')
+ </if>
+ </where>
+ ORDER BY groups.name
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml b/sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml
new file mode 100644
index 00000000000..95567f2e5c2
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.permission.PermissionTemplateMapper">
+
+ <insert id="insert" parameterType="PermissionTemplate" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO permission_templates (name, kee, description, key_pattern, created_at, updated_at)
+ VALUES (#{name}, #{kee}, #{description}, #{keyPattern}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <update id="update" parameterType="PermissionTemplate">
+ UPDATE permission_templates
+ SET name = #{name}, description = #{description}, key_pattern = #{keyPattern}, updated_at = #{updatedAt}
+ WHERE id = #{id}
+ </update>
+
+ <delete id="delete" parameterType="long">
+ DELETE FROM permission_templates
+ WHERE id = #{templateId}
+ </delete>
+
+ <delete id="deleteUsersPermissions" parameterType="long">
+ DELETE FROM perm_templates_users
+ WHERE template_id = #{templateId}
+ </delete>
+
+ <delete id="deleteGroupsPermissions" parameterType="long">
+ DELETE FROM perm_templates_groups
+ WHERE template_id = #{templateId}
+ </delete>
+
+ <insert id="insertUserPermission" parameterType="PermissionTemplateUser">
+ INSERT INTO perm_templates_users (template_id, user_id, permission_reference, created_at, updated_at)
+ VALUES (#{templateId}, #{userId}, #{permission}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <delete id="deleteUserPermission" parameterType="PermissionTemplateUser">
+ DELETE FROM perm_templates_users
+ WHERE template_id = #{templateId}
+ AND user_id = #{userId}
+ AND permission_reference = #{permission}
+ </delete>
+
+ <insert id="insertGroupPermission" parameterType="PermissionTemplateGroup">
+ INSERT INTO perm_templates_groups (template_id, group_id, permission_reference, created_at, updated_at)
+ VALUES (#{templateId}, #{groupId}, #{permission}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <delete id="deleteGroupPermission" parameterType="PermissionTemplateGroup">
+ DELETE FROM perm_templates_groups
+ WHERE template_id = #{templateId}
+ AND permission_reference = #{permission}
+ AND
+ <choose>
+ <when test="groupId != null">
+ group_id = #{groupId}
+ </when>
+ <otherwise>
+ group_id IS NULL
+ </otherwise>
+ </choose>
+ </delete>
+
+ <delete id="deleteByGroupId" parameterType="long">
+ DELETE FROM perm_templates_groups
+ WHERE group_id = #{groupId}
+ </delete>
+
+ <select id="selectUsers" parameterType="map" resultType="UserWithPermission">
+ SELECT u.login as login, u.name as name, ptu.permission_reference as permission
+ FROM users u
+ LEFT JOIN perm_templates_users ptu ON ptu.user_id=u.id
+ AND ptu.permission_reference=#{query.permission}
+ AND ptu.template_id=#{templateId}
+ <where>
+ u.active = ${_true}
+ <choose>
+ <when test="query.membership() == 'IN'">
+ AND ptu.permission_reference IS NOT NULL
+ </when>
+ <when test="query.membership() == 'OUT'">
+ AND ptu.permission_reference IS NULL
+ </when>
+ </choose>
+ <if test="query.search() != null">
+ AND (UPPER(u.name) LIKE #{query.searchSql} ESCAPE '/')
+ </if>
+ </where>
+ ORDER BY u.name
+ </select>
+
+ <select id="selectGroups" parameterType="map" resultType="GroupWithPermission">
+ SELECT name, description, permission FROM
+ (SELECT g.name as name, g.description as description, ptg.permission_reference as permission
+ FROM groups g
+ LEFT JOIN perm_templates_groups ptg ON ptg.group_id=g.id
+ AND ptg.permission_reference=#{query.permission}
+ AND ptg.template_id=#{templateId}
+ UNION
+ -- Add Anyone group permission
+ SELECT #{anyoneGroup} as name, NULL as description, ptg.permission_reference as permission
+ FROM perm_templates_groups ptg
+ <where>
+ AND ptg.permission_reference=#{query.permission}
+ AND ptg.template_id=#{templateId}
+ AND ptg.group_id IS NULL
+ </where>
+ ) groups
+ <where>
+ <if test="query.search() != null">
+ AND (UPPER(groups.name) LIKE #{query.searchSql} ESCAPE '/')
+ </if>
+ </where>
+ ORDER BY groups.name
+ </select>
+
+ <select id="selectByKey" parameterType="String" resultType="PermissionTemplate">
+ SELECT id, name, kee, description, key_pattern AS keyPattern, created_at AS createdAt, updated_at AS updatedAt
+ FROM permission_templates
+ WHERE kee = #{kee}
+ </select>
+
+ <select id="selectAllPermissionTemplates" resultType="PermissionTemplate">
+ SELECT id, name, kee, description, key_pattern AS keyPattern, created_at AS createdAt, updated_at AS updatedAt
+ FROM permission_templates
+ </select>
+
+ <select id="selectTemplateUsersPermissions" parameterType="String" resultMap="fullPermissionsTemplateResult">
+ SELECT pt.id AS template_id,
+ pt.name AS template_name,
+ pt.description AS template_description,
+ pt.key_pattern AS template_key_pattern,
+ pt.created_at AS template_created_at,
+ pt.updated_at AS template_updated_at,
+ ptu.id AS permission_template_user_id,
+ ptu.permission_reference AS user_permission,
+ ptu.user_id AS user_id,
+ u.name AS user_name,
+ u.login AS user_login
+ FROM permission_templates pt
+ INNER JOIN perm_templates_users ptu ON ptu.template_id = pt.id
+ INNER JOIN users u ON u.id = ptu.user_id AND u.active = ${_true}
+ WHERE pt.kee = #{templateKey}
+ </select>
+
+ <select id="selectTemplateGroupsPermissions" parameterType="String" resultMap="fullPermissionsTemplateResult">
+ SELECT pt.id AS template_id,
+ pt.name AS template_name,
+ pt.description AS template_description,
+ pt.key_pattern AS template_key_pattern,
+ pt.created_at AS template_created_at,
+ pt.updated_at AS template_updated_at,
+ ptg.id AS permission_template_group_id,
+ ptg.permission_reference AS group_permission,
+ ptg.group_id AS group_id,
+ g.name AS group_name
+ FROM permission_templates pt
+ INNER JOIN perm_templates_groups ptg ON ptg.template_id = pt.id
+ LEFT OUTER JOIN groups g ON g.id = ptg.group_id
+ WHERE pt.kee = #{templateKey}
+ AND (g.name IS NOT NULL OR ptg.group_id IS NULL)
+ </select>
+
+ <resultMap id="fullPermissionsTemplateResult" type="PermissionTemplate">
+ <id property="id" column="template_id"/>
+ <result property="name" column="template_name"/>
+ <result property="description" column="template_description"/>
+ <result property="keyPattern" column="template_key_pattern"/>
+ <result property="createdAt" column="template_created_at"/>
+ <result property="updatedAt" column="template_updated_at"/>
+ <collection property="usersPermissions" ofType="PermissionTemplateUser">
+ <id property="id" column="permission_template_user_id"/>
+ <result property="userId" column="user_id"/>
+ <result property="permission" column="user_permission"/>
+ <result property="userName" column="user_name"/>
+ <result property="userLogin" column="user_login"/>
+ </collection>
+ <collection property="groupsPermissions" ofType="PermissionTemplateGroup">
+ <id property="id" column="permission_template_group_id"/>
+ <result property="groupId" column="group_id"/>
+ <result property="permission" column="group_permission"/>
+ <result property="groupName" column="group_name"/>
+ </collection>
+ </resultMap>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml b/sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
new file mode 100644
index 00000000000..d36c205772c
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.property.PropertiesMapper">
+
+ <select id="findUsersForNotification" parameterType="map" resultType="String">
+ select u.login
+ from users u
+ inner join properties p on p.user_id=u.id
+ <if test="projectUuid == null">
+ where p.prop_key = #{notifKey} and p.text_value LIKE 'true' and p.resource_id is null
+ </if>
+ <if test="projectUuid != null">
+ inner join projects c on c.id=p.resource_id
+ where p.prop_key = #{notifKey} AND p.text_value LIKE 'true'
+ and c.uuid = #{projectUuid} and p.resource_id is not null
+ </if>
+ </select>
+
+ <select id="findNotificationSubscribers" parameterType="map" resultType="String">
+ SELECT U.login
+ FROM properties P, users U
+ WHERE P.user_id = U.id AND P.prop_key = #{propKey} AND P.text_value LIKE 'true'
+ AND (
+ P.resource_id is null
+ <if test="componentKey != null">
+ OR P.resource_id in (select id from projects where kee=#{componentKey})
+ </if>
+ )
+ </select>
+
+ <select id="selectGlobalProperties" resultType="Property">
+ select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
+ from properties p
+ where p.resource_id is null and p.user_id is null
+ </select>
+
+ <select id="selectProjectProperties" parameterType="String" resultType="Property">
+ select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
+ from properties p, projects r
+ where p.resource_id=r.id and p.user_id is null and r.kee=#{resourceKey}
+ </select>
+
+ <select id="selectProjectPropertiesByResourceId" parameterType="Long" resultType="Property">
+ select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
+ from properties p
+ where p.resource_id=#{resourceId} and p.user_id is null
+ </select>
+
+ <select id="selectDescendantModuleProperties" parameterType="String" resultType="Property">
+ SELECT prop.id as id, prop.prop_key as "key", prop.text_value as value, prop.resource_id as resourceId, prop.user_id
+ as userId
+ FROM properties prop
+ INNER JOIN (SELECT p.id FROM projects p<include refid="org.sonar.db.component.ComponentMapper.modulesTreeQuery"/>)
+ modules on modules.id=prop.resource_id
+ WHERE prop.user_id IS NULL
+ </select>
+
+ <select id="selectSetOfResourceProperties" parameterType="map" resultType="Property">
+ select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
+ from properties p
+ where p.resource_id=#{rId} and p.prop_key in
+ <foreach item="propKey" index="index" collection="propKeys" open="(" separator="," close=")">#{propKey}</foreach>
+ </select>
+
+ <select id="selectByKey" parameterType="map" resultType="Property">
+ select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
+ from properties p
+ where p.prop_key=#{key}
+ <if test="resourceId == null">
+ AND p.resource_id is null
+ </if>
+ <if test="resourceId != null">
+ AND p.resource_id=#{resourceId}
+ </if>
+ <if test="userId == null">
+ AND p.user_id is null
+ </if>
+ <if test="userId != null">
+ AND p.user_id=#{userId}
+ </if>
+ </select>
+
+ <select id="selectByQuery" parameterType="map" resultType="Property">
+ select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
+ from properties p
+ <where>
+ <if test="query.key() != null">
+ AND p.prop_key=#{query.key}
+ </if>
+ <if test="query.componentId() != null">
+ AND p.resource_id=#{query.componentId}
+ </if>
+ <if test="query.userId() != null">
+ AND p.user_id=#{query.userId}
+ </if>
+ </where>
+ </select>
+
+ <update id="update" parameterType="Property">
+ update properties set text_value = #{value} where id = #{id}
+ </update>
+
+ <insert id="insert" parameterType="Property" useGeneratedKeys="false">
+ INSERT INTO properties (prop_key, resource_id, user_id, text_value)
+ VALUES (#{key}, #{resourceId}, #{userId}, #{value})
+ </insert>
+
+ <delete id="deleteProjectProperty" parameterType="map">
+ delete from properties where prop_key=#{key} and resource_id=#{rId} and user_id is null
+ </delete>
+
+ <delete id="deleteProjectProperties" parameterType="map">
+ DELETE FROM properties
+ WHERE
+ prop_key=#{key}
+ AND text_value LIKE #{value}
+ AND resource_id IS NOT NULL
+ AND user_id IS NULL
+ </delete>
+
+ <delete id="deleteGlobalProperty" parameterType="string">
+ delete from properties where prop_key=#{id} and resource_id is null and user_id is null
+ </delete>
+
+ <delete id="deleteGlobalProperties">
+ delete from properties where resource_id is null and user_id is null
+ </delete>
+
+ <delete id="deleteAllProperties" parameterType="string">
+ delete from properties where prop_key=#{id}
+ </delete>
+
+ <update id="renamePropertyKey" parameterType="map">
+ update properties set prop_key=#{newKey} where prop_key=#{oldKey}
+ </update>
+
+ <update id="updateProperties" parameterType="map">
+ update properties set text_value=#{newValue} where text_value LIKE #{oldValue} and prop_key=#{key}
+ </update>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
new file mode 100644
index 00000000000..1d6bed47806
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
@@ -0,0 +1,341 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.purge.PurgeMapper">
+
+ <select id="selectSnapshotIds" parameterType="map" resultType="long">
+ select s.id from snapshots s
+ <where>
+ <if test="islast != null">
+ and s.islast=#{islast}
+ </if>
+ <if test="notPurged != null and notPurged">
+ and (s.purge_status is null or s.purge_status=0)
+ </if>
+ <if test="rootSnapshotId != null">
+ and s.root_snapshot_id=#{rootSnapshotId}
+ </if>
+ <if test="id != null">
+ and s.id=#{id}
+ </if>
+ <if test="rootProjectId != null">
+ and s.root_project_id=#{rootProjectId}
+ </if>
+ <if test="resourceId != null">
+ and s.project_id=#{resourceId}
+ </if>
+ <if test="status != null">
+ and s.status in
+ <foreach item="s" index="index" collection="status" open="(" separator="," close=")">#{s}</foreach>
+ </if>
+ <if test="scopes != null">
+ and s.scope in
+ <foreach item="scope" index="index" collection="scopes" open="(" separator="," close=")">#{scope}</foreach>
+ </if>
+ <if test="qualifiers != null">
+ and s.qualifier in
+ <foreach item="qualifier" index="index" collection="qualifiers" open="(" separator="," close=")">#{qualifier}
+ </foreach>
+ </if>
+ <if test="withVersionEvent != null">
+ <if test="withVersionEvent">
+ and exists(select e.id from events e where e.snapshot_id=s.id and e.category='Version')
+ </if>
+ <if test="!withVersionEvent">
+ and not exists(select e.id from events e where e.snapshot_id=s.id and e.category='Version')
+ </if>
+ </if>
+ </where>
+ </select>
+
+ <select id="selectSnapshotIdsByResource" parameterType="map" resultType="long">
+ select s.id from snapshots s
+ <where>
+ s.project_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </where>
+ </select>
+
+ <select id="selectPurgeableSnapshotsWithEvents" parameterType="long" resultType="PurgeableSnapshot">
+ select s.id as "snapshotId", s.created_at as "date", ${_true} as "hasEvents", islast as "isLast" from
+ snapshots s where
+ s.project_id=#{id} and s.status='P' and s.qualifier &lt;&gt; 'LIB' and
+ exists(select e.id from events e where e.snapshot_id=s.id)
+ </select>
+
+ <select id="selectPurgeableSnapshotsWithoutEvents" parameterType="long" resultType="PurgeableSnapshot">
+ select s.id as "snapshotId", s.created_at as "date", ${_false} as "hasEvents", islast as "isLast" from
+ snapshots s where
+ s.project_id=#{id} and s.status='P' and s.qualifier &lt;&gt; 'LIB' and
+ not exists(select e.id from events e where e.snapshot_id=s.id)
+ </select>
+
+ <select id="selectComponentIdUuidsToDisable" resultType="IdUuidPair" parameterType="long">
+ select p.id, p.uuid from projects p
+ where (p.id=#{id} or p.root_id=#{id}) and p.enabled=${_true}
+ and not exists(select s.project_id from snapshots s where s.islast=${_true} and s.project_id=p.id)
+ </select>
+
+ <select id="selectPurgeableFileUuids" resultType="string" parameterType="long">
+ select p.uuid from projects p
+ where (p.id=#{id} or p.root_id=#{id}) and p.enabled=${_true} and p.scope='FIL'
+ and not exists(select s.project_id from snapshots s where s.islast=${_true} and s.project_id=p.id)
+ </select>
+
+ <select id="selectMetricIdsWithoutHistoricalData" resultType="long">
+ select id from metrics where delete_historical_data=${_true}
+ </select>
+
+ <select id="selectProjectIdUuidsByRootId" resultType="IdUuidPair" parameterType="long">
+ select id, uuid from projects where root_id=#{id} and scope='PRJ'
+ </select>
+
+ <select id="selectComponentIdUuidsByRootId" resultType="IdUuidPair" parameterType="long">
+ select id, uuid from projects where root_id=#{id} or id=#{id}
+ </select>
+
+ <delete id="deleteSnapshotMeasures" parameterType="map">
+ delete from project_measures where snapshot_id in
+ <foreach collection="snapshotIds" open="(" close=")" item="snapshotId" separator=",">
+ #{snapshotId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSnapshotDuplications" parameterType="map">
+ delete from duplications_index where snapshot_id in
+ <foreach collection="snapshotIds" open="(" close=")" item="snapshotId" separator=",">
+ #{snapshotId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSnapshotEvents" parameterType="map">
+ delete from events where snapshot_id in
+ <foreach collection="snapshotIds" open="(" close=")" item="snapshotId" separator=",">
+ #{snapshotId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSnapshot" parameterType="map">
+ delete from snapshots where id in
+ <foreach collection="snapshotIds" open="(" close=")" item="snapshotId" separator=",">
+ #{snapshotId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteSnapshotWastedMeasures" parameterType="map">
+ delete from project_measures
+ <where>
+ snapshot_id in
+ <foreach collection="snapshotIds" open="(" close=")" item="snapshotId" separator=",">
+ #{snapshotId}
+ </foreach>
+ and (rule_id is not null or person_id is not null
+ <if test="mids.size()>0">
+ or metric_id in
+ <foreach item="mid" index="index" collection="mids" open="(" separator="," close=")">#{mid}</foreach>
+ </if>
+ )
+ </where>
+ </delete>
+
+ <update id="updatePurgeStatusToOne" parameterType="long">
+ update snapshots set purge_status = 1 where id = #{id}
+ </update>
+
+ <update id="disableResource" parameterType="long">
+ update projects set enabled=${_false} where id=#{id}
+ </update>
+
+ <update id="resolveResourceIssuesNotAlreadyResolved" parameterType="map">
+ UPDATE issues SET status='CLOSED',resolution='REMOVED',updated_at=#{dateAsLong},issue_close_date=#{dateAsLong},
+ issue_update_date=#{dateAsLong}
+ WHERE component_uuid=#{componentUuid} AND resolution IS NULL
+ </update>
+
+ <delete id="deleteResourceIndex" parameterType="map">
+ delete from resource_index where resource_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteEvent" parameterType="map">
+ delete from events where id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResourceLinks" parameterType="map">
+ delete from project_links where component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResourceProperties" parameterType="map">
+ delete from properties where resource_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResource" parameterType="map">
+ delete from projects where id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResourceGroupRoles" parameterType="map">
+ delete from group_roles where resource_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResourceUserRoles" parameterType="map">
+ delete from user_roles where resource_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResourceManualMeasures" parameterType="map">
+ delete from manual_measures where component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ </delete>
+
+ <delete id="deleteComponentEvents" parameterType="map">
+ delete from events where component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ </delete>
+
+ <delete id="deleteResourceActionPlans" parameterType="map">
+ delete from action_plans where project_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <delete id="deleteAuthors" parameterType="map">
+ delete from authors where person_id in
+ <foreach collection="resourceIds" open="(" close=")" item="resourceId" separator=",">
+ #{resourceId}
+ </foreach>
+ </delete>
+
+ <update id="setSnapshotIsLastToFalse" parameterType="long">
+ update snapshots set islast=${_false} where project_id=#{id}
+ </update>
+
+ <delete id="deleteComponentIssueChanges" parameterType="map">
+ delete from issue_changes ic
+ where exists (select * from issues i where i.kee=ic.issue_key and i.component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ )
+ </delete>
+
+ <!-- Mssql -->
+ <delete id="deleteComponentIssueChanges" databaseId="mssql" parameterType="map">
+ delete issue_changes from issue_changes
+ inner join issues on issue_changes.issue_key=issues.kee
+ where issues.component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ </delete>
+
+ <!-- Mysql -->
+ <delete id="deleteComponentIssueChanges" databaseId="mysql" parameterType="map">
+ delete ic from issue_changes as ic, issues as i where ic.issue_key=i.kee and i.component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ </delete>
+
+ <delete id="deleteComponentIssues" parameterType="map">
+ delete from issues where component_uuid in
+ <foreach collection="componentUuids" open="(" close=")" item="componentUuid" separator=",">
+ #{componentUuid}
+ </foreach>
+ </delete>
+
+ <delete id="deleteFileSourcesByProjectUuid">
+ delete from file_sources where project_uuid=#{rootProjectUuid}
+ </delete>
+
+ <delete id="deleteFileSourcesByUuid">
+ delete from file_sources where file_uuid=#{fileUuid}
+ </delete>
+
+ <delete id="deleteOldClosedIssueChanges" parameterType="map">
+ delete from issue_changes ic
+ where exists (
+ select * from issues i
+ where i.project_uuid=#{projectUuid} and i.kee=ic.issue_key
+ <choose>
+ <when test="toDate == null">
+ and i.issue_close_date is not null
+ </when>
+ <otherwise>
+ and i.issue_close_date &lt; #{toDate}
+ </otherwise>
+ </choose>
+ )
+ </delete>
+
+ <!-- Mssql -->
+ <delete id="deleteOldClosedIssueChanges" databaseId="mssql" parameterType="map">
+ delete issue_changes from issue_changes
+ inner join issues on issue_changes.issue_key=issues.kee
+ where issues.project_uuid=#{projectUuid}
+ <choose>
+ <when test="toDate == null">
+ and issues.issue_close_date is not null
+ </when>
+ <otherwise>
+ and issues.issue_close_date &lt; #{toDate}
+ </otherwise>
+ </choose>
+ </delete>
+
+ <!-- Mysql -->
+ <delete id="deleteOldClosedIssueChanges" databaseId="mysql" parameterType="map">
+ delete ic
+ from issue_changes as ic, issues as i
+ where i.project_uuid=#{projectUuid}
+ and ic.issue_key=i.kee
+ <choose>
+ <when test="toDate == null">
+ and i.issue_close_date is not null
+ </when>
+ <otherwise>
+ and i.issue_close_date &lt; #{toDate}
+ </otherwise>
+ </choose>
+ </delete>
+
+ <delete id="deleteOldClosedIssues" parameterType="map">
+ delete from issues
+ where project_uuid=#{projectUuid}
+ <choose>
+ <when test="toDate == null">
+ and issue_close_date is not null
+ </when>
+ <otherwise>
+ and issue_close_date &lt; #{toDate}
+ </otherwise>
+ </choose>
+ </delete>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml b/sonar-db/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml
new file mode 100644
index 00000000000..bbcf7b52121
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualitygate.ProjectQgateAssociationMapper">
+
+ <select id="selectProjects" parameterType="map" resultType="ProjectQgateAssociation">
+ SELECT proj.id as id, proj.name as name, prop.text_value as gateId
+ FROM projects proj
+ <if test="query.projectSearch() != null">
+ JOIN resource_index ind ON ind.root_project_id=proj.id
+ </if>
+ LEFT JOIN properties prop ON prop.resource_id=proj.id AND prop.prop_key='sonar.qualitygate' AND prop.text_value LIKE
+ #{gateId}
+ <where>
+ <choose>
+ <when test="query.membership() == 'selected'">
+ AND prop.text_value IS NOT NULL
+ </when>
+ <when test="query.membership() == 'deselected'">
+ AND prop.text_value IS NULL
+ </when>
+ </choose>
+ <if test="query.projectSearch() != null">
+ AND ind.kee LIKE #{query.projectSearchSql}
+ </if>
+ AND proj.qualifier='TRK'
+ AND proj.scope='PRJ'
+ <if test="query.projectSearch() != null">
+ AND ind.qualifier='TRK'
+ </if>
+ </where>
+ ORDER BY proj.name
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml b/sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml
new file mode 100644
index 00000000000..0923db5ae60
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateConditionMapper.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualitygate.QualityGateConditionMapper">
+
+ <insert id="insert" parameterType="QualityGateCondition" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ insert into quality_gate_conditions (qgate_id, metric_id, operator, value_error, value_warning, period, created_at,
+ updated_at)
+ values (#{qualityGateId}, #{metricId}, #{operator}, #{errorThreshold}, #{warningThreshold}, #{period}, #{createdAt},
+ #{updatedAt})
+ </insert>
+
+ <sql id="conditionColumns">
+ id, qgate_id as qualityGateId, metric_id as metricId, operator, value_warning as warningThreshold, value_error as
+ errorThreshold, period,
+ created_at as createdAt, updated_at as updatedAt
+ </sql>
+
+ <select id="selectForQualityGate" resultType="QualityGateCondition" parameterType="long">
+ select
+ <include refid="conditionColumns"/>
+ from quality_gate_conditions where qgate_id=#{qGateId}
+ order by id asc
+ </select>
+
+ <select id="selectById" parameterType="long" resultType="QualityGateCondition">
+ select
+ <include refid="conditionColumns"/>
+ from quality_gate_conditions where id=#{id}
+ </select>
+
+ <update id="delete" parameterType="long">
+ delete from quality_gate_conditions where id=#{id}
+ </update>
+
+ <update id="update" parameterType="QualityGateCondition">
+ update quality_gate_conditions set
+ metric_id=#{metricId},
+ operator=#{operator},
+ value_warning=#{warningThreshold},
+ value_error=#{errorThreshold},
+ period=#{period},
+ updated_at=#{updatedAt}
+ where id=#{id}
+ </update>
+
+ <delete id="deleteConditionsWithInvalidMetrics">
+ delete from quality_gate_conditions
+ where metric_id not in (select id from metrics where enabled=${_true})
+ </delete>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml b/sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
new file mode 100644
index 00000000000..64d2e58efb4
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualitygate.QualityGateMapper">
+
+ <insert id="insert" parameterType="QualityGate" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ insert into quality_gates (name, created_at, updated_at)
+ values (#{name}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <sql id="gateColumns">
+ id, name, created_at as createdAt, updated_at as updatedAt
+ </sql>
+
+ <select id="selectAll" resultType="QualityGate">
+ select
+ <include refid="gateColumns"/>
+ from quality_gates
+ order by name asc
+ </select>
+
+ <select id="selectByName" parameterType="String" resultType="QualityGate">
+ select
+ <include refid="gateColumns"/>
+ from quality_gates
+ where name=#{name}
+ </select>
+
+ <select id="selectById" parameterType="long" resultType="QualityGate">
+ select
+ <include refid="gateColumns"/>
+ from quality_gates
+ where id=#{id}
+ </select>
+
+ <update id="delete" parameterType="long">
+ delete from quality_gates where id=#{id}
+ </update>
+
+ <update id="update" parameterType="QualityGate">
+ update quality_gates set
+ name=#{name},
+ updated_at=#{updatedAt}
+ where id=#{id}
+ </update>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml b/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
new file mode 100644
index 00000000000..23ac091ce8a
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualityprofile.ActiveRuleMapper">
+
+ <sql id="activeRuleKeyColumns">
+ a.id,
+ a.profile_id as "profileId",
+ a.rule_id as "ruleId",
+ a.failure_level as "severity",
+ a.inheritance as "inheritance",
+ r.plugin_rule_key as "rulefield",
+ r.plugin_name as "repository",
+ qp.kee as "profileKey",
+ a.created_at as "createdAt",
+ a.updated_at as "updatedAt"
+ </sql>
+
+ <sql id="activeRuleKeyJoin">
+ LEFT JOIN rules_profiles qp ON qp.id=a.profile_id
+ LEFT JOIN rules r ON r.id = a.rule_id
+ </sql>
+
+
+ <sql id="activeRuleColumns">
+ a.id,
+ a.profile_id as profileId,
+ a.rule_id as ruleId,
+ a.failure_level as severity,
+ a.inheritance as inheritance,
+ active_rule_parent.id as parentId,
+ a.created_at as "createdAt",
+ a.updated_at as "updatedAt"
+ </sql>
+
+ <sql id="activeRuleJoin">
+ LEFT JOIN rules_profiles qp ON qp.id=a.profile_id
+ LEFT JOIN rules_profiles profile_parent ON profile_parent.kee=qp.parent_kee
+ LEFT JOIN active_rules active_rule_parent ON active_rule_parent.profile_id=profile_parent.id AND
+ a.rule_id=active_rule_parent.rule_id
+ </sql>
+
+ <select id="selectAfterDate" parameterType="Date" resultType="ActiveRule"
+ fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
+ select
+ <include refid="activeRuleKeyColumns"/>
+ from active_rules a
+ <include refid="activeRuleKeyJoin"/>
+ <where>
+ <if test="date != null">
+ a.updated_at IS NULL or a.updated_at &gt;= #{date}
+ </if>
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="ActiveRule" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO active_rules (profile_id, rule_id, failure_level, inheritance, created_at, updated_at)
+ VALUES (#{profileId}, #{ruleId}, #{severity}, #{inheritance}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <update id="update" parameterType="ActiveRule">
+ UPDATE active_rules SET
+ profile_id=#{profileId},
+ rule_id=#{ruleId},
+ failure_level=#{severity},
+ inheritance=#{inheritance},
+ updated_at=#{updatedAt}
+ WHERE id=#{id}
+ </update>
+
+ <update id="delete" parameterType="int">
+ DELETE FROM active_rules WHERE id=#{id}
+ </update>
+
+ <select id="selectByIds" parameterType="map" resultType="ActiveRule">
+ select
+ <include refid="activeRuleColumns"/>
+ from active_rules a
+ <include refid="activeRuleJoin"/>
+ <where>
+ (<foreach collection="list" item="id" open="(" separator=" or " close=")">
+ a.id=#{id}
+ </foreach>)
+ </where>
+ </select>
+
+ <select id="selectById" parameterType="int" resultType="ActiveRule">
+ SELECT
+ <include refid="activeRuleColumns"/>
+ FROM active_rules a
+ <include refid="activeRuleJoin"/>
+ WHERE a.id=#{id}
+ </select>
+
+
+ <select id="selectByKey" parameterType="map" resultType="ActiveRule">
+ SELECT
+ <include refid="activeRuleKeyColumns"/>
+ FROM active_rules a
+ <include refid="activeRuleKeyJoin"/>
+ WHERE
+ qp.kee = #{profileKey}
+ AND r.plugin_rule_key = #{rule}
+ AND r.plugin_name = #{repository}
+ </select>
+
+ <select id="selectByProfileKey" parameterType="string" resultType="ActiveRule">
+ SELECT
+ <include refid="activeRuleKeyColumns"/>
+ FROM active_rules a
+ <include refid="activeRuleKeyJoin"/>
+ where qp.kee=#{id}
+ </select>
+
+ <select id="selectByRuleId" parameterType="Integer" resultType="ActiveRule">
+ SELECT
+ <include refid="activeRuleKeyColumns"/>
+ FROM active_rules a
+ <include refid="activeRuleKeyJoin"/>
+ WHERE a.rule_id=#{ruleId}
+ </select>
+
+ <select id="selectAll" parameterType="map" resultType="ActiveRule">
+ select
+ <include refid="activeRuleColumns"/>
+ from active_rules a
+ <include refid="activeRuleJoin"/>
+ </select>
+
+ <!-- Parameters -->
+
+ <sql id="activeRuleParamColumns">
+ p.id,
+ p.active_rule_id as activeRuleId,
+ p.rules_parameter_id as rulesParameterId,
+ p.rules_parameter_key as kee,
+ p.value as value
+ </sql>
+
+ <insert id="insertParameter" parameterType="ActiveRuleParam" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO active_rule_parameters (active_rule_id, rules_parameter_id, rules_parameter_key, value)
+ VALUES (#{activeRuleId}, #{rulesParameterId}, #{key}, #{value})
+ </insert>
+
+ <update id="updateParameter" parameterType="ActiveRuleParam">
+ UPDATE active_rule_parameters SET
+ active_rule_id=#{activeRuleId},
+ rules_parameter_id=#{rulesParameterId},
+ rules_parameter_key=#{key},
+ value=#{value}
+ WHERE id=#{id}
+ </update>
+
+ <update id="deleteParameters" parameterType="int">
+ DELETE FROM active_rule_parameters WHERE active_rule_id=#{id}
+ </update>
+
+ <update id="deleteParameter" parameterType="int">
+ DELETE FROM active_rule_parameters WHERE id=#{id}
+ </update>
+
+ <select id="selectParamsByActiveRuleId" parameterType="Integer" resultType="ActiveRuleParam">
+ select
+ <include refid="activeRuleParamColumns"/>
+ from active_rule_parameters p
+ <where>
+ p.active_rule_id=#{id}
+ </where>
+ </select>
+
+ <select id="selectParamsByActiveRuleIds" parameterType="map" resultType="ActiveRuleParam">
+ select
+ <include refid="activeRuleParamColumns"/>
+ from active_rule_parameters p
+ <where>
+ (<foreach collection="list" item="id" open="(" separator=" or " close=")">
+ p.active_rule_id=#{id}
+ </foreach>)
+ </where>
+ </select>
+
+ <select id="selectParamByActiveRuleAndKey" parameterType="map" resultType="ActiveRuleParam">
+ SELECT
+ <include refid="activeRuleParamColumns"/>
+ FROM active_rule_parameters p
+ <where>
+ AND p.active_rule_id=#{activeRuleId}
+ AND p.rules_parameter_key=#{key}
+ </where>
+ </select>
+
+ <select id="selectParamsByProfileKey" parameterType="string" resultType="ActiveRuleParam">
+ select
+ <include refid="activeRuleParamColumns"/>
+ from active_rule_parameters p
+ inner join active_rules ar on ar.id=p.active_rule_id
+ inner join rules_profiles rp on rp.id=ar.profile_id
+ where rp.kee=#{id}
+ </select>
+
+ <select id="selectAllParams" resultType="ActiveRuleParam">
+ select
+ <include refid="activeRuleParamColumns"/>
+ from active_rule_parameters p
+ </select>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml b/sonar-db/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml
new file mode 100644
index 00000000000..873a983c4cb
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.qualityprofile.QualityProfileMapper">
+
+ <sql id="profilesColumns">
+ p.id as id,
+ p.kee as kee,
+ p.name as name,
+ p.language as language,
+ p.parent_kee as parentKee,
+ p.is_default as isDefault,
+ p.created_at as createdAt,
+ p.updated_at as updatedAt,
+ p.rules_updated_at as rulesUpdatedAt
+ </sql>
+
+ <insert id="insert" parameterType="QualityProfile" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO rules_profiles (kee, parent_kee, name, language, is_default, created_at, updated_at, rules_updated_at)
+ VALUES (#{kee}, #{parentKee}, #{name}, #{language}, #{isDefault}, #{createdAt}, #{updatedAt}, #{rulesUpdatedAt,})
+ </insert>
+
+ <update id="update" parameterType="QualityProfile">
+ UPDATE rules_profiles SET
+ name=#{name},
+ language=#{language},
+ is_default=#{isDefault},
+ parent_kee=#{parentKee},
+ updated_at=#{updatedAt},
+ rules_updated_at=#{rulesUpdatedAt}
+ WHERE id=#{id}
+ </update>
+
+ <update id="delete" parameterType="int">
+ DELETE FROM rules_profiles WHERE id=#{id}
+ </update>
+
+ <select id="selectAll" parameterType="map" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ ORDER BY p.name, p.language
+ </select>
+
+ <select id="selectByNameAndLanguage" parameterType="map" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ WHERE p.name=#{name} AND p.language=#{language}
+ </select>
+
+ <select id="selectByKey" parameterType="string" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ WHERE p.kee=#{id}
+ </select>
+
+ <select id="selectByLanguage" parameterType="String" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ WHERE p.language=#{language}
+ ORDER BY p.name
+ </select>
+
+ <select id="selectById" parameterType="Integer" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ WHERE p.id=#{id}
+ </select>
+
+ <select id="selectParent" parameterType="string" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ INNER JOIN rules_profiles child ON child.parent_kee=p.kee AND child.kee=#{id}
+ </select>
+
+ <select id="selectParentById" parameterType="int" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ INNER JOIN rules_profiles child ON child.parent_kee=p.kee and child.id=#{id}
+ </select>
+
+ <select id="selectChildren" parameterType="string" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ WHERE p.parent_kee=#{id}
+ ORDER BY p.name
+ </select>
+
+ <select id="selectDefaultProfile" parameterType="map" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ WHERE p.is_default=${_true}
+ AND p.language=#{language}
+ </select>
+
+ <select id="selectProjects" resultType="Component">
+ SELECT projects.id as id, projects.name as name, projects.kee as kee, projects.uuid as uuid
+ FROM projects projects
+ JOIN project_qprofiles pp ON pp.project_uuid = projects.uuid
+ JOIN rules_profiles prof ON pp.profile_key = prof.kee
+ <where>
+ AND prof.name = #{profileName}
+ AND prof.language = #{language}
+ </where>
+ </select>
+
+ <select id="selectSelectedProjects" resultType="org.sonar.db.qualityprofile.ProjectQprofileAssociationDto">
+ SELECT pp.id as id, pj.id as projectId, pj.uuid as projectUuid, pj.name as projectName, pp.profile_key as profileKey
+ FROM projects pj
+ JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
+ AND pp.profile_key = #{profileKey}
+ <where>
+ AND pj.scope='PRJ' AND pj.qualifier='TRK'
+ AND UPPER(pj.name) LIKE #{nameQuery}
+ </where>
+ ORDER BY pj.name ASC
+ </select>
+
+ <select id="selectDeselectedProjects" resultType="org.sonar.db.qualityprofile.ProjectQprofileAssociationDto">
+ SELECT pp.id as id, pj.id as projectId, pj.uuid as projectUuid, pj.name as projectName, pp.profile_key as profileKey
+ FROM projects pj
+ LEFT JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
+ AND pp.profile_key = #{profileKey}
+ <where>
+ AND pj.scope='PRJ' AND pj.qualifier='TRK'
+ AND UPPER(pj.name) LIKE #{nameQuery}
+ AND pp.profile_key IS NULL
+ </where>
+ ORDER BY pj.name ASC
+ </select>
+
+ <select id="selectProjectAssociations" resultType="org.sonar.db.qualityprofile.ProjectQprofileAssociationDto">
+ SELECT pp.id as id, pj.id as projectId, pj.uuid as projectUuid, pj.name as projectName, pp.profile_key as profileKey
+ FROM projects pj
+ LEFT JOIN project_qprofiles pp ON pp.project_uuid = pj.uuid
+ AND pp.profile_key = #{profileKey}
+ <where>
+ AND pj.scope='PRJ' AND pj.qualifier='TRK'
+ AND UPPER(pj.name) LIKE #{nameQuery}
+ </where>
+ ORDER BY pj.name ASC
+ </select>
+
+ <select id="countProjects" parameterType="Integer" resultType="Integer">
+ SELECT count(projects.id)
+ FROM projects projects
+ JOIN project_qprofiles pp ON pp.project_uuid=projects.uuid
+ JOIN rules_profiles prof ON pp.profile_key=prof.kee
+ <where>
+ AND prof.language=#{language}
+ AND prof.name=#{profileName}
+ </where>
+ </select>
+
+ <select id="countProjectsByProfile" resultType="org.sonar.db.qualityprofile.QualityProfileProjectCount">
+ SELECT pp.profile_key as profileKey, count(projects.id) as projectCount
+ FROM projects projects
+ INNER JOIN project_qprofiles pp ON pp.project_uuid=projects.uuid
+ INNER JOIN rules_profiles prof ON pp.profile_key=prof.kee
+ WHERE projects.enabled=${_true}
+ GROUP BY pp.profile_key
+ </select>
+
+ <select id="selectByProjectIdAndLanguage" parameterType="map" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ JOIN project_qprofiles pp ON pp.profile_key=p.kee
+ JOIN projects project ON pp.project_uuid=project.uuid
+ AND project.id=#{projectId}
+ WHERE p.language=#{language}
+ </select>
+
+ <select id="selectByProjectAndLanguage" parameterType="map" resultType="QualityProfile">
+ SELECT
+ <include refid="profilesColumns"/>
+ FROM rules_profiles p
+ JOIN project_qprofiles pp ON pp.profile_key=p.kee
+ JOIN projects project ON pp.project_uuid=project.uuid
+ AND project.kee=#{projectKey}
+ WHERE p.language=#{language}
+ </select>
+
+ <insert id="insertProjectProfileAssociation" keyColumn="id" useGeneratedKeys="true">
+ INSERT INTO project_qprofiles (project_uuid, profile_key) VALUES (#{projectUuid}, #{profileKey})
+ </insert>
+
+ <update id="updateProjectProfileAssociation">
+ UPDATE project_qprofiles SET profile_key=#{profileKey} WHERE project_uuid=#{projectUuid}
+ </update>
+
+ <update id="deleteProjectProfileAssociation">
+ DELETE FROM project_qprofiles WHERE project_uuid=#{projectUuid} AND profile_key=#{profileKey}
+ </update>
+
+ <update id="deleteAllProjectProfileAssociation">
+ DELETE FROM project_qprofiles WHERE profile_key=#{profileKey}
+ </update>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml b/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml
new file mode 100644
index 00000000000..fd93ba7b0a5
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/rule/RuleMapper.xml
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.rule.RuleMapper">
+
+ <sql id="selectColumns">
+ r.id,
+ r.plugin_rule_key as "ruleKey",
+ r.plugin_name as "repositoryKey",
+ r.description,
+ r.description_format as "descriptionFormat",
+ r.status,
+ r.name,
+ r.plugin_config_key as "configKey",
+ r.priority as "severity",
+ r.is_template as "isTemplate",
+ r.language as "language",
+ r.template_id as "templateId",
+ r.note_data as "noteData",
+ r.note_user_login as "noteUserLogin",
+ r.note_created_at as "noteCreatedAt",
+ r.note_updated_at as "noteUpdatedAt",
+ r.characteristic_id as "subCharacteristicId",
+ r.default_characteristic_id as "defaultSubCharacteristicId",
+ r.remediation_function as "remediationFunction",
+ r.default_remediation_function as "defaultRemediationFunction",
+ r.remediation_coeff as "remediationCoefficient",
+ r.default_remediation_coeff as "defaultRemediationCoefficient",
+ r.remediation_offset as "remediationOffset",
+ r.default_remediation_offset as "defaultRemediationOffset",
+ r.effort_to_fix_description as "effortToFixDescription",
+ r.tags as "tagsField",
+ r.system_tags as "systemTagsField",
+ r.created_at as "createdAt",
+ r.updated_at as "updatedAt"
+ </sql>
+
+ <select id="selectAll" resultType="Rule">
+ select
+ <include refid="selectColumns"/>
+ from rules r
+ </select>
+
+ <select id="selectAfterDate" resultType="Rule" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
+ select
+ <include refid="selectColumns"/>
+ from rules r
+ <where>
+ <if test="date != null">
+ r.updated_at IS NULL or r.updated_at &gt;= #{date}
+ </if>
+ </where>
+ </select>
+
+ <select id="selectEnablesAndNonManual" resultType="Rule">
+ select
+ <include refid="selectColumns"/>
+ from rules r
+ where r.status != 'REMOVED' and r.plugin_name != 'manual'
+ </select>
+
+ <select id="selectById" parameterType="Integer" resultType="Rule">
+ select
+ <include refid="selectColumns"/>
+ from rules r WHERE r.id=#{id}
+ </select>
+
+ <select id="selectByKey" parameterType="map" resultType="Rule">
+ SELECT
+ <include refid="selectColumns"/>
+ FROM rules r WHERE r.plugin_name=#{repository} AND r.plugin_rule_key=#{rule}
+ </select>
+
+ <select id="selectByName" parameterType="String" resultType="Rule">
+ select
+ <include refid="selectColumns"/>
+ from rules r WHERE r.name=#{name}
+ </select>
+
+ <select id="selectNonManual" resultType="Rule">
+ select
+ <include refid="selectColumns"/>
+ from rules r
+ where r.plugin_name != 'manual'
+ </select>
+
+ <select id="selectBySubCharacteristicId" resultType="Rule">
+ select
+ <include refid="selectColumns"/>
+ from rules r
+ where (r.characteristic_id=#{subCharacteristicId} or r.default_characteristic_id=#{subCharacteristicId})
+ </select>
+
+ <update id="update" parameterType="Rule">
+ UPDATE rules SET
+ plugin_rule_key=#{ruleKey},
+ plugin_name=#{repositoryKey},
+ description=#{description},
+ description_format=#{descriptionFormat},
+ status=#{status},
+ name=#{name},
+ plugin_config_key=#{configKey},
+ priority=#{severity},
+ is_template=#{isTemplate},
+ language=#{language},
+ template_id=#{templateId},
+ note_data=#{noteData},
+ note_user_login=#{noteUserLogin},
+ note_created_at=#{noteCreatedAt},
+ note_updated_at=#{noteUpdatedAt},
+ characteristic_id=#{subCharacteristicId},
+ default_characteristic_id=#{defaultSubCharacteristicId},
+ remediation_function=#{remediationFunction},
+ default_remediation_function=#{defaultRemediationFunction},
+ remediation_coeff=#{remediationCoefficient},
+ default_remediation_coeff=#{defaultRemediationCoefficient},
+ remediation_offset=#{remediationOffset},
+ default_remediation_offset=#{defaultRemediationOffset},
+ effort_to_fix_description=#{effortToFixDescription},
+ updated_at=#{updatedAt},
+ tags=#{tagsField},
+ system_tags=#{systemTagsField}
+ WHERE id=#{id}
+ </update>
+
+ <sql id="insertColumns">
+ (plugin_rule_key, plugin_name, description, description_format, status, name, plugin_config_key, priority,
+ is_template, language, template_id,
+ characteristic_id, default_characteristic_id, remediation_function, default_remediation_function,
+ remediation_coeff, default_remediation_coeff, remediation_offset, default_remediation_offset,
+ effort_to_fix_description, tags, system_tags, note_data, note_user_login, note_created_at, note_updated_at,
+ created_at, updated_at)
+ </sql>
+
+ <insert id="insert" parameterType="Rule" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ insert into rules
+ <include refid="insertColumns"/>
+ values (#{ruleKey}, #{repositoryKey}, #{description}, #{descriptionFormat}, #{status}, #{name}, #{configKey},
+ #{severity}, #{isTemplate}, #{language}, #{templateId},
+ #{subCharacteristicId}, #{defaultSubCharacteristicId}, #{remediationFunction}, #{defaultRemediationFunction},
+ #{remediationCoefficient}, #{defaultRemediationCoefficient}, #{remediationOffset}, #{defaultRemediationOffset},
+ #{effortToFixDescription}, #{tagsField}, #{systemTagsField}, #{noteData}, #{noteUserLogin}, #{noteCreatedAt},
+ #{noteUpdatedAt}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <insert id="batchInsert" parameterType="Rule" useGeneratedKeys="false">
+ insert into rules
+ <include refid="insertColumns"/>
+ values (#{ruleKey,jdbcType=VARCHAR}, #{repositoryKey,jdbcType=VARCHAR}, #{description,jdbcType=CLOB},
+ #{descriptionFormat,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
+ #{configKey,jdbcType=VARCHAR},
+ #{severity,jdbcType=INTEGER}, #{isTemplate,jdbcType=BOOLEAN}, #{language,jdbcType=VARCHAR},
+ #{templateId,jdbcType=INTEGER},
+ #{subCharacteristicId,jdbcType=INTEGER}, #{defaultSubCharacteristicId,jdbcType=INTEGER},
+ #{remediationFunction,jdbcType=VARCHAR}, #{defaultRemediationFunction,jdbcType=VARCHAR},
+ #{remediationCoefficient,jdbcType=VARCHAR}, #{defaultRemediationCoefficient,jdbcType=VARCHAR},
+ #{remediationOffset,jdbcType=VARCHAR}, #{defaultRemediationOffset,jdbcType=VARCHAR},
+ #{effortToFixDescription}, #{tagsField}, #{systemTagsField}, #{noteData}, #{noteUserLogin}, #{noteCreatedAt},
+ #{noteUpdatedAt,jdbcType=TIMESTAMP}, #{createdAt,jdbcType=TIMESTAMP}, #{updatedAt,jdbcType=TIMESTAMP})
+ </insert>
+
+ <delete id="deleteParams" parameterType="Integer">
+ delete from active_rule_parameters where rules_parameter_id=#{id}
+ </delete>
+
+ <sql id="paramColumns">
+ p.id as "id", p.rule_id as "ruleId", p.name as "name", p.param_type as "type", p.default_value as "defaultValue",
+ p.description as "description"
+ </sql>
+
+ <select id="selectAllParams" resultType="RuleParam">
+ select
+ <include refid="paramColumns"/>
+ from rules_parameters p
+ </select>
+
+ <select id="selectParamsByRuleIds" resultType="RuleParam">
+ SELECT
+ <include refid="paramColumns"/>
+ FROM rules_parameters p
+ <where>
+ AND (<foreach item="id" index="index" collection="ruleIds" open="(" separator=" or " close=")">
+ p.rule_id=#{id}</foreach>)
+ </where>
+ </select>
+
+ <select id="selectParamsByRuleKey" resultType="RuleParam" parameterType="org.sonar.api.rule.RuleKey">
+ SELECT
+ <include refid="paramColumns"/>
+ FROM rules_parameters p, rules r
+ WHERE p.rule_id=r.id
+ AND r.plugin_name=#{repository} AND r.plugin_rule_key=#{rule}
+ </select>
+
+ <select id="selectParamByRuleAndKey" resultType="RuleParam">
+ SELECT
+ <include refid="paramColumns"/>
+ FROM rules_parameters p
+ WHERE p.rule_id=#{ruleId} AND p.name=#{key}
+ </select>
+
+ <delete id="deleteParameter" parameterType="Integer">
+ delete from rules_parameters where id=#{id}
+ </delete>
+
+ <insert id="insertParameter" parameterType="RuleParam" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO rules_parameters (rule_id, name, param_type, default_value, description)
+ VALUES (#{ruleId}, #{name}, #{type}, #{defaultValue}, #{description})
+ </insert>
+
+ <update id="updateParameter" parameterType="RuleParam">
+ UPDATE rules_parameters SET
+ param_type=#{type},
+ default_value=#{defaultValue},
+ description=#{description}
+ WHERE id=#{id}
+ </update>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/semaphore/SemaphoreMapper.xml b/sonar-db/src/main/resources/org/sonar/db/semaphore/SemaphoreMapper.xml
new file mode 100644
index 00000000000..5d1c1028478
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/semaphore/SemaphoreMapper.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.semaphore.SemaphoreMapper">
+
+ <insert id="initialize" parameterType="map" useGeneratedKeys="false">
+ INSERT INTO semaphores (name, checksum, created_at, updated_at, locked_at)
+ VALUES (#{name}, #{checksum}, #{createdAt}, #{updatedAt}, #{lockedAt})
+ </insert>
+
+ <update id="acquire" parameterType="map">
+ update semaphores
+ set updated_at = #{now}, locked_at = #{now}
+ where name=#{name}
+ <if test="updatedBefore != null">
+ AND updated_at &lt; #{updatedBefore}
+ </if>
+ </update>
+
+ <delete id="release" parameterType="String">
+ delete from semaphores where name=#{id}
+ </delete>
+
+ <select id="selectSemaphore" parameterType="String" resultType="Semaphore">
+ select s.id, s.name as name, s.locked_at as lockedAt, s.created_at as createdAt, s.updated_at as updatedAt
+ from semaphores s where s.name=#{name}
+ </select>
+
+ <update id="update" parameterType="map">
+ update semaphores
+ set updated_at = #{now}
+ where name=#{name}
+ </update>
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/source/FileSourceMapper.xml b/sonar-db/src/main/resources/org/sonar/db/source/FileSourceMapper.xml
new file mode 100644
index 00000000000..dd6a83ed5ff
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/source/FileSourceMapper.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.source.FileSourceMapper">
+
+ <select id="select" parameterType="map" resultType="org.sonar.db.source.FileSourceDto">
+ SELECT id, project_uuid as projectUuid, file_uuid as fileUuid, created_at as createdAt, updated_at as updatedAt,
+ binary_data as binaryData, line_hashes as lineHashes, data_hash as dataHash, src_hash as srcHash, data_type as
+ dataType
+ FROM file_sources
+ WHERE file_uuid = #{fileUuid} and data_type = #{dataType}
+ </select>
+
+ <select id="selectHashesForProject" parameterType="map" resultType="org.sonar.db.source.FileSourceDto">
+ SELECT id, file_uuid as fileUuid, data_hash as dataHash, src_hash as srcHash, updated_at as updatedAt
+ FROM file_sources
+ WHERE project_uuid = #{projectUuid} and data_type=#{dataType}
+ </select>
+
+ <insert id="insert" parameterType="org.sonar.db.source.FileSourceDto" useGeneratedKeys="false">
+ INSERT INTO file_sources (project_uuid, file_uuid, created_at, updated_at, binary_data, line_hashes, data_hash,
+ src_hash, data_type)
+ VALUES (#{projectUuid,jdbcType=VARCHAR}, #{fileUuid,jdbcType=VARCHAR}, #{createdAt,jdbcType=BIGINT},
+ #{updatedAt,jdbcType=BIGINT}, #{binaryData,jdbcType=BLOB}, #{lineHashes,jdbcType=CLOB},
+ #{dataHash,jdbcType=VARCHAR}, #{srcHash,jdbcType=VARCHAR},#{dataType,jdbcType=VARCHAR})
+ </insert>
+
+ <update id="update" parameterType="org.sonar.db.source.FileSourceDto" useGeneratedKeys="false">
+ UPDATE file_sources SET
+ updated_at = #{updatedAt,jdbcType=BIGINT},
+ binary_data = #{binaryData,jdbcType=BLOB},
+ line_hashes = #{lineHashes,jdbcType=CLOB},
+ data_hash = #{dataHash,jdbcType=VARCHAR},
+ src_hash = #{srcHash,jdbcType=VARCHAR}
+ WHERE id = #{id}
+ </update>
+
+ <update id="updateDateWhenUpdatedDateIsZero" parameterType="org.sonar.db.source.FileSourceDto"
+ useGeneratedKeys="false">
+ UPDATE file_sources SET
+ updated_at = #{date,jdbcType=BIGINT}
+ WHERE project_uuid = #{projectUuid}
+ AND updated_at = 0
+ </update>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/source/SnapshotDataMapper.xml b/sonar-db/src/main/resources/org/sonar/db/source/SnapshotDataMapper.xml
new file mode 100644
index 00000000000..022381e4df8
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/source/SnapshotDataMapper.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.source.SnapshotDataMapper">
+
+ <sql id="snapshotDataColumns">
+ snapshot_id AS "snapshotId",
+ snapshot_data AS "data",
+ data_type AS "dataType"
+ </sql>
+
+ <select id="selectSnapshotData" parameterType="map" resultType="SnapshotData">
+ SELECT
+ <include refid="snapshotDataColumns"/>
+ FROM snapshot_data
+ WHERE snapshot_id = #{sid}
+ AND data_type IN
+ <foreach item="dataType" index="index" collection="dataTypes" open="(" separator="," close=")">#{dataType}</foreach>
+ </select>
+
+ <select id="selectSnapshotDataByComponentKey" parameterType="map" resultType="SnapshotData">
+ SELECT
+ <include refid="snapshotDataColumns"/>
+ FROM snapshot_data sd
+ INNER JOIN projects p ON p.id=sd.resource_id and p.enabled=${_true}
+ WHERE p.kee = #{componentKey}
+ AND data_type IN
+ <foreach item="dataType" index="index" collection="dataTypes" open="(" separator="," close=")">#{dataType}</foreach>
+ </select>
+
+ <insert id="insert" parameterType="SnapshotData" useGeneratedKeys="false">
+ insert into snapshot_data
+ (resource_id, snapshot_id, snapshot_data, data_type, created_at, updated_at)
+ values (
+ #{resourceId,jdbcType=INTEGER}, #{snapshotId,jdbcType=INTEGER}, #{data,jdbcType=CLOB}, #{dataType,jdbcType=VARCHAR},
+ CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ </insert>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/AuthorMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/AuthorMapper.xml
new file mode 100644
index 00000000000..440d2d74701
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/AuthorMapper.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.AuthorMapper">
+
+ <select id="selectByLogin" parameterType="string" resultType="Author">
+ SELECT id, person_id AS personId, login, created_at AS createdAt, updated_at AS updatedAt
+ FROM authors WHERE login=#{id}
+ </select>
+
+ <select id="countDeveloperLogins" parameterType="long" resultType="int">
+ SELECT count(id)
+ FROM authors WHERE person_id=#{id}
+ </select>
+
+ <insert id="insert" parameterType="Author" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO authors (person_id, login, created_at, updated_at)
+ VALUES (#{personId}, #{login},
+ #{createdAt}, #{updatedAt})
+ </insert>
+
+ <select id="selectScmAccountsByDeveloperUuids" parameterType="String" resultType="String">
+ SELECT a.login
+ FROM authors a
+ INNER JOIN projects p ON p.id=a.person_id
+ <where>
+ and p.uuid in
+ <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">
+ #{uuid}
+ </foreach>
+ </where>
+ </select>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/AuthorizationMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/AuthorizationMapper.xml
new file mode 100644
index 00000000000..f33a7ae0640
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/AuthorizationMapper.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.AuthorizationMapper">
+
+ <select id="keepAuthorizedComponentKeysForUser" parameterType="map" resultType="string">
+ SELECT p.kee
+ FROM group_roles gr, projects p
+ WHERE
+ gr.role=#{role}
+ and (gr.group_id is null or gr.group_id in (select gu.group_id from groups_users gu where gu.user_id=#{userId}))
+ and (gr.resource_id = p.root_id or gr.resource_id = p.id) and
+ <foreach collection="componentKeys" open="(" close=")" item="element" index="index" separator=" or ">
+ p.kee=#{element}
+ </foreach>
+ UNION
+ SELECT p.kee
+ FROM user_roles ur
+ INNER JOIN projects p on p.id = ur.resource_id
+ WHERE
+ ur.role=#{role}
+ and ur.user_id=#{userId} and
+ <foreach collection="componentKeys" open="(" close=")" item="element" index="index" separator=" or ">
+ p.kee=#{element}
+ </foreach>
+ </select>
+
+ <select id="keepAuthorizedComponentKeysForAnonymous" parameterType="map" resultType="string">
+ SELECT p.kee
+ FROM group_roles gr, projects p
+ WHERE
+ gr.role=#{role}
+ and gr.group_id is null
+ and (gr.resource_id = p.root_id or gr.resource_id = p.id) and
+ <foreach collection="componentKeys" open="(" close=")" item="element" index="index" separator=" or ">
+ p.kee=#{element}
+ </foreach>
+ </select>
+
+ <select id="keepAuthorizedProjectIdsForUser" parameterType="map" resultType="long">
+ SELECT gr.resource_id
+ FROM group_roles gr
+ WHERE
+ gr.role=#{role}
+ and (gr.group_id is null or gr.group_id in (select gu.group_id from groups_users gu where gu.user_id=#{userId}))
+ and
+ <foreach collection="componentIds" open="(" close=")" item="element" index="index" separator=" or ">
+ gr.resource_id=#{element}
+ </foreach>
+ UNION
+ SELECT p.id
+ FROM user_roles ur
+ INNER JOIN projects p on p.id = ur.resource_id
+ WHERE
+ ur.role=#{role}
+ and ur.user_id=#{userId} and
+ <foreach collection="componentIds" open="(" close=")" item="element" index="index" separator=" or ">
+ p.id=#{element}
+ </foreach>
+ </select>
+
+ <select id="keepAuthorizedProjectIdsForAnonymous" parameterType="map" resultType="long">
+ SELECT gr.resource_id
+ FROM group_roles gr
+ WHERE
+ gr.role=#{role}
+ and gr.group_id is null
+ and
+ <foreach collection="componentIds" open="(" close=")" item="element" index="index" separator=" or ">
+ gr.resource_id=#{element}
+ </foreach>
+ </select>
+
+ <select id="selectAuthorizedRootProjectsKeys" parameterType="map" resultType="string">
+ <include refid="selectAuthorizedRootProjectsKeysQuery"/>
+ </select>
+
+ <sql id="selectAuthorizedRootProjectsKeysQuery">
+ <choose>
+ <when test="userId != null">
+ SELECT p.kee as root_project_kee
+ FROM group_roles gr
+ INNER JOIN projects p on p.id = gr.resource_id AND p.module_uuid IS NULL
+ <where>
+ and gr.role=#{role}
+ and (gr.group_id is null or gr.group_id in (select gu.group_id from groups_users gu where
+ gu.user_id=#{userId}))
+ </where>
+ UNION
+ SELECT p.kee as root_project_kee
+ FROM user_roles ur
+ INNER JOIN projects p on p.id = ur.resource_id AND p.module_uuid IS NULL
+ <where>
+ and ur.role=#{role}
+ and ur.user_id = #{userId}
+ </where>
+ </when>
+ <otherwise>
+ SELECT p.kee as root_project_kee
+ FROM group_roles gr
+ INNER JOIN projects p on p.id = gr.resource_id AND p.module_uuid IS NULL
+ <where>
+ and gr.role=#{role}
+ and gr.group_id is null
+ </where>
+ </otherwise>
+ </choose>
+ </sql>
+
+ <select id="selectAuthorizedRootProjectsUuids" parameterType="map" resultType="string">
+ <choose>
+ <when test="userId != null">
+ SELECT p.uuid as root_project_uuid
+ FROM group_roles gr
+ INNER JOIN projects p on p.id = gr.resource_id AND p.module_uuid IS NULL
+ <where>
+ and gr.role=#{role}
+ and (gr.group_id is null or gr.group_id in (select gu.group_id from groups_users gu where
+ gu.user_id=#{userId}))
+ </where>
+ UNION
+ SELECT p.uuid as root_project_uuid
+ FROM user_roles ur
+ INNER JOIN projects p on p.id = ur.resource_id AND p.module_uuid IS NULL
+ <where>
+ and ur.role=#{role}
+ and ur.user_id = #{userId}
+ </where>
+ </when>
+ <otherwise>
+ SELECT p.uuid as root_project_uuid
+ FROM group_roles gr
+ INNER JOIN projects p on p.id = gr.resource_id AND p.module_uuid IS NULL
+ <where>
+ and gr.role=#{role}
+ and gr.group_id is null
+ </where>
+ </otherwise>
+ </choose>
+ </select>
+
+ <!-- same as selectAuthorizedRootProjectsKeysQuery but returns ids instead of keys -->
+ <sql id="selectAuthorizedRootProjectIdsQuery">
+ <choose>
+ <when test="userId != null">
+ SELECT p.id as root_project_id
+ FROM group_roles gr
+ INNER JOIN projects p on p.id = gr.resource_id AND p.module_uuid IS NULL
+ <where>
+ and gr.role=#{role}
+ and (gr.group_id is null or gr.group_id in (select gu.group_id from groups_users gu where
+ gu.user_id=#{userId}))
+ </where>
+ UNION
+ SELECT p.id as root_project_id
+ FROM user_roles ur
+ INNER JOIN projects p on p.id = ur.resource_id AND p.module_uuid IS NULL
+ <where>
+ and ur.role=#{role}
+ and ur.user_id = #{userId}
+ </where>
+ </when>
+ <otherwise>
+ SELECT p.id as root_project_id
+ FROM group_roles gr
+ INNER JOIN projects p on p.id = gr.resource_id AND p.module_uuid IS NULL
+ <where>
+ and gr.role=#{role}
+ and gr.group_id is null
+ </where>
+ </otherwise>
+ </choose>
+ </sql>
+
+ <select id="selectGlobalPermissions" parameterType="map" resultType="String">
+ <choose>
+ <when test="userLogin != null">
+ SELECT gr.role
+ FROM group_roles gr
+ INNER JOIN groups_users gu on gu.group_id=gr.group_id
+ INNER JOIN users u on u.id=gu.user_id
+ <where>
+ and u.login=#{userLogin}
+ and gr.resource_id is null
+ </where>
+ UNION
+ SELECT gr.role
+ FROM group_roles gr
+ WHERE gr.group_id IS NULL AND gr.resource_id IS NULL
+ UNION
+ SELECT ur.role
+ FROM user_roles ur
+ INNER JOIN users u on u.id=ur.user_id
+ <where>
+ and u.login=#{userLogin}
+ and ur.resource_id is null
+ </where>
+ </when>
+ <otherwise>
+ SELECT gr.role
+ FROM group_roles gr
+ <where>
+ and gr.resource_id is null
+ and gr.group_id is null
+ </where>
+ </otherwise>
+ </choose>
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/GroupMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/GroupMapper.xml
new file mode 100644
index 00000000000..0991bcffc0b
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/GroupMapper.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.GroupMapper">
+
+ <sql id="groupColumns">
+ g.id as id,
+ g.name as name,
+ g.description as description,
+ g.created_at as "createdAt",
+ g.updated_at as "updatedAt"
+ </sql>
+
+ <select id="selectByKey" parameterType="string" resultType="Group">
+ SELECT
+ <include refid="groupColumns"/>
+ FROM groups g
+ <where>
+ g.name=#{id}
+ </where>
+ </select>
+
+ <select id="selectById" parameterType="long" resultType="Group">
+ SELECT
+ <include refid="groupColumns"/>
+ FROM groups g
+ <where>
+ g.id=#{id}
+ </where>
+ </select>
+
+ <delete id="deleteById" parameterType="long">
+ DELETE FROM groups
+ <where>
+ id=#{id}
+ </where>
+ </delete>
+
+ <select id="selectByUserLogin" parameterType="string" resultType="Group">
+ SELECT
+ <include refid="groupColumns"/>
+ FROM groups g
+ INNER JOIN groups_users gu on gu.group_id=g.id
+ INNER JOIN users u on u.id=gu.user_id
+ <where>
+ u.login=#{login}
+ </where>
+ </select>
+
+ <insert id="insert" parameterType="Group" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO groups (name, description, created_at, updated_at)
+ VALUES (#{name}, #{description}, #{createdAt}, #{updatedAt})
+ </insert>
+
+ <update id="update" parameterType="Group">
+ UPDATE groups SET
+ name=#{name},
+ description=#{description},
+ updated_at=#{updatedAt}
+ WHERE id=#{id}
+ </update>
+
+ <select id="selectByQuery" parameterType="map" resultType="Group">
+ SELECT
+ <include refid="groupColumns"/>
+ FROM groups g
+ WHERE UPPER(g.name) LIKE #{query}
+ ORDER BY UPPER(g.name)
+ </select>
+
+ <select id="countByQuery" parameterType="map" resultType="int">
+ SELECT count(g.id)
+ FROM groups g
+ WHERE UPPER(g.name) LIKE #{query}
+ </select>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/GroupMembershipMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/GroupMembershipMapper.xml
new file mode 100644
index 00000000000..36591ceea05
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/GroupMembershipMapper.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.GroupMembershipMapper">
+
+ <sql id="commonClauses">
+ FROM groups g
+ LEFT JOIN groups_users gu ON gu.group_id=g.id AND gu.user_id=#{userId}
+ <where>
+ <choose>
+ <when test="query.membership() == 'IN'">
+ AND gu.user_id IS NOT NULL
+ </when>
+ <when test="query.membership() == 'OUT'">
+ AND gu.user_id IS NULL
+ </when>
+ </choose>
+ <if test="query.groupSearch() != null">
+ AND (UPPER(g.name) LIKE #{query.groupSearchSql} ESCAPE '/')
+ </if>
+ </where>
+ </sql>
+
+ <select id="selectGroups" parameterType="map" resultType="GroupMembership">
+ SELECT g.id as id, g.name as name, g.description as description, gu.user_id as userId
+ <include refid="commonClauses"/>
+ ORDER BY g.name
+ </select>
+
+ <select id="countGroups" parameterType="map" resultType="int">
+ SELECT COUNT(g.id)
+ <include refid="commonClauses"/>
+ </select>
+
+ <select id="countUsersByGroup" parameterType="long" resultType="org.sonar.db.user.GroupUserCount">
+ SELECT g.name as groupName, count(gu.user_id) as userCount
+ FROM groups g
+ LEFT JOIN groups_users gu ON gu.group_id=g.id
+ <where>
+ g.id in
+ <foreach collection="groupIds" open="(" close=")" item="id" separator=",">
+ #{id}
+ </foreach>
+ </where>
+ GROUP BY g.name
+ </select>
+
+ <select id="selectGroupsByLogins" parameterType="string" resultType="org.sonar.db.user.LoginGroup">
+ SELECT u.login as login, g.name as groupName
+ FROM users u
+ LEFT JOIN groups_users gu ON gu.user_id=u.id
+ INNER JOIN groups g ON gu.group_id=g.id
+ <where>
+ u.login in
+ <foreach collection="logins" open="(" close=")" item="login" separator=",">
+ #{login}
+ </foreach>
+ </where>
+ </select>
+
+ <sql id="userCommonClauses">
+ FROM users u
+ LEFT JOIN groups_users gu ON gu.user_id=u.id AND gu.group_id=#{groupId}
+ <where>
+ <choose>
+ <when test="query.membership() == 'IN'">
+ AND gu.group_id IS NOT NULL
+ </when>
+ <when test="query.membership() == 'OUT'">
+ AND gu.group_id IS NULL
+ </when>
+ </choose>
+ <if test="query.memberSearch() != null">
+ AND ((UPPER(u.login) LIKE #{query.memberSearchSql} ESCAPE '/') OR (UPPER(u.name) LIKE #{query.memberSearchSql}
+ ESCAPE '/'))
+ </if>
+ AND u.active=${_true}
+ </where>
+ </sql>
+
+ <select id="selectMembers" parameterType="map" resultType="org.sonar.db.user.UserMembershipDto">
+ SELECT u.id as id, u.login as login, u.name as name, gu.group_id as groupId
+ <include refid="userCommonClauses"/>
+ ORDER BY u.name ASC
+ </select>
+
+ <select id="countMembers" parameterType="map" resultType="int">
+ SELECT COUNT(u.id)
+ <include refid="userCommonClauses"/>
+ </select>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/RoleMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/RoleMapper.xml
new file mode 100644
index 00000000000..41303206013
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/RoleMapper.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.RoleMapper">
+
+ <select id="selectUserPermissions" parameterType="map" resultType="String">
+ SELECT ur.role
+ FROM user_roles ur
+ INNER JOIN users u ON u.id=ur.user_id AND u.active=${_true}
+ <where>
+ AND u.login = #{userLogin}
+ <choose>
+ <when test="resourceId != null">
+ AND resource_id=#{resourceId}
+ </when>
+ <otherwise>
+ AND resource_id IS NULL
+ </otherwise>
+ </choose>
+ </where>
+ </select>
+
+ <select id="selectGroupPermissions" parameterType="map" resultType="String">
+ SELECT gr.role
+ FROM group_roles gr
+ <if test="isAnyOneGroup != true">
+ INNER JOIN groups g ON g.id = gr.group_id
+ </if>
+ <where>
+ <choose>
+ <when test="isAnyOneGroup != true">
+ AND g.name = #{groupName}
+ </when>
+ <otherwise>
+ AND gr.group_id IS NULL
+ </otherwise>
+ </choose>
+ <choose>
+ <when test="resourceId != null">
+ AND resource_id=#{resourceId}
+ </when>
+ <otherwise>
+ AND resource_id IS NULL
+ </otherwise>
+ </choose>
+ </where>
+ </select>
+
+ <insert id="insertGroupRole" parameterType="GroupRole" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO group_roles (group_id, resource_id, role)
+ VALUES (#{groupId}, #{resourceId}, #{role})
+ </insert>
+
+ <insert id="insertUserRole" parameterType="UserRole" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO user_roles (user_id, resource_id, role)
+ VALUES (#{userId}, #{resourceId}, #{role})
+ </insert>
+
+ <delete id="deleteGroupRole" parameterType="map">
+ DELETE FROM group_roles
+ WHERE role=#{role}
+ AND
+ <choose>
+ <when test="resourceId != null">
+ resource_id=#{resourceId}
+ </when>
+ <otherwise>
+ resource_id IS NULL
+ </otherwise>
+ </choose>
+ AND
+ <choose>
+ <when test="groupId != null">
+ group_id=#{groupId}
+ </when>
+ <otherwise>
+ group_id IS NULL
+ </otherwise>
+ </choose>
+ </delete>
+
+ <delete id="deleteUserRole" parameterType="map">
+ DELETE FROM user_roles
+ WHERE user_id=#{userId}
+ AND role=#{role}
+ AND
+ <choose>
+ <when test="resourceId != null">
+ resource_id=#{resourceId}
+ </when>
+ <otherwise>
+ resource_id IS NULL
+ </otherwise>
+ </choose>
+ </delete>
+
+ <delete id="deleteGroupRolesByResourceId" parameterType="long">
+ delete from group_roles where resource_id=#{id}
+ </delete>
+
+ <delete id="deleteUserRolesByResourceId" parameterType="long">
+ delete from user_roles where resource_id=#{id}
+ </delete>
+
+ <select id="countResourceUserRoles" parameterType="long" resultType="int">
+ SELECT count(id)
+ FROM user_roles WHERE resource_id=#{id}
+ </select>
+
+ <select id="countResourceGroupRoles" parameterType="long" resultType="int">
+ SELECT count(id)
+ FROM group_roles WHERE resource_id=#{id}
+ </select>
+
+ <delete id="deleteGroupRolesByGroupId" parameterType="long">
+ delete from group_roles where group_id=#{id}
+ </delete>
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/UserGroupMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/UserGroupMapper.xml
new file mode 100644
index 00000000000..d2177b452c0
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/UserGroupMapper.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.UserGroupMapper">
+
+ <insert id="insert" parameterType="UserGroup" useGeneratedKeys="false">
+ INSERT INTO groups_users (user_id, group_id)
+ VALUES (#{userId}, #{groupId})
+ </insert>
+
+ <delete id="delete" parameterType="UserGroup">
+ DELETE FROM groups_users
+ <where>
+ AND user_id = #{userId}
+ AND group_id = #{groupId}
+ </where>
+ </delete>
+
+ <delete id="deleteMembersByGroup" parameterType="long">
+ DELETE FROM groups_users
+ <where>
+ AND group_id = #{groupId}
+ </where>
+ </delete>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
new file mode 100644
index 00000000000..d46d44f2eaa
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.UserMapper">
+
+ <sql id="userColumns">
+ u.id as id,
+ u.login as login,
+ u.name as name,
+ u.email as email,
+ u.active as "active",
+ u.scm_accounts as "scmAccounts",
+ u.salt as "salt",
+ u.crypted_password as "cryptedPassword",
+ u.created_at as "createdAt",
+ u.updated_at as "updatedAt"
+ </sql>
+
+ <select id="selectByLogin" parameterType="String" resultType="User">
+ SELECT
+ <include refid="userColumns"/>
+ FROM users u
+ WHERE u.login=#{login}
+ </select>
+
+ <select id="selectNullableByScmAccountOrLoginOrEmail" parameterType="map" resultType="User">
+ SELECT
+ <include refid="userColumns"/>
+ FROM users u
+ WHERE
+ u.login=#{scmAccount}
+ OR u.email=#{scmAccount}
+ OR u.scm_accounts like #{likeScmAccount}
+ </select>
+
+ <select id="selectUser" parameterType="long" resultType="User">
+ SELECT
+ <include refid="userColumns"/>
+ FROM users u
+ WHERE u.id=#{id}
+ </select>
+
+ <select id="selectUserByLogin" parameterType="string" resultType="User">
+ SELECT
+ <include refid="userColumns"/>
+ FROM users u
+ WHERE u.login=#{id} AND u.active=${_true}
+ </select>
+
+ <select id="selectUsersByLogins" parameterType="map" resultType="User">
+ SELECT
+ <include refid="userColumns"/>
+ FROM users u WHERE
+ (<foreach item="login" index="index" collection="logins" open="(" separator=" or " close=")">
+ u.login=#{login}
+ </foreach>)
+ </select>
+
+ <select id="selectUsers" parameterType="map" resultType="User">
+ SELECT
+ <include refid="userColumns"/>
+ FROM users u
+ <where>
+ <if test="logins != null and logins.size() > 0">
+ u.login IN
+ <foreach item="login" index="index" collection="logins" open="(" separator="," close=")">
+ #{login}
+ </foreach>
+ </if>
+ <if test="includeDeactivated==false">
+ AND u.active=${_true}
+ </if>
+ <if test="searchText != null">
+ AND (u.login LIKE #{searchTextSql} ESCAPE '/' OR u.name LIKE #{searchTextSql} ESCAPE '/')
+ </if>
+ </where>
+ ORDER BY u.name
+ </select>
+
+ <select id="selectGroupByName" parameterType="string" resultType="Group">
+ SELECT id, name, description, created_at AS "createdAt", updated_at AS "updatedAt"
+ FROM groups WHERE name=#{id}
+ </select>
+
+ <delete id="removeUserFromGroups" parameterType="long">
+ DELETE FROM groups_users WHERE user_id=#{id}
+ </delete>
+
+ <delete id="deleteUserRoles" parameterType="long">
+ DELETE FROM user_roles WHERE user_id=#{id}
+ </delete>
+
+ <delete id="deleteUserProperties" parameterType="long">
+ DELETE FROM properties WHERE user_id=#{id}
+ </delete>
+
+ <delete id="deleteUnsharedUserDashboards" parameterType="long">
+ DELETE FROM dashboards WHERE user_id=#{id} and shared &lt;&gt; ${_true}
+ </delete>
+
+ <delete id="deleteUserActiveDashboards" parameterType="long">
+ DELETE FROM active_dashboards WHERE user_id=#{id}
+ </delete>
+
+ <delete id="deleteUnsharedUserMeasureFilters" parameterType="long">
+ DELETE FROM measure_filters WHERE user_id=#{id} and shared &lt;&gt; ${_true}
+ </delete>
+
+ <delete id="deleteUserMeasureFilterFavourites" parameterType="long">
+ DELETE FROM measure_filter_favourites WHERE user_id=#{id}
+ </delete>
+
+ <delete id="deleteUnsharedUserIssueFilters" parameterType="String">
+ DELETE FROM issue_filters WHERE user_login=#{id} and shared &lt;&gt; ${_true}
+ </delete>
+
+ <delete id="deleteUserIssueFilterFavourites" parameterType="String">
+ DELETE FROM issue_filter_favourites WHERE user_login=#{id}
+ </delete>
+
+ <update id="deactivateUser" parameterType="long">
+ UPDATE users SET active=${_false}, updated_at=#{now,jdbcType=BIGINT} WHERE id=#{id}
+ </update>
+
+ <insert id="insert" parameterType="User" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO users (login, name, email, active, scm_accounts, salt, crypted_password, created_at, updated_at)
+ VALUES (#{login,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{active,jdbcType=BOOLEAN},
+ #{scmAccounts,jdbcType=VARCHAR},
+ #{salt,jdbcType=VARCHAR}, #{cryptedPassword,jdbcType=VARCHAR}, #{createdAt,jdbcType=BIGINT},
+ #{updatedAt,jdbcType=BIGINT})
+ </insert>
+
+ <insert id="update" parameterType="User" useGeneratedKeys="false">
+ UPDATE users set name=#{name,jdbcType=VARCHAR}, email=#{email,jdbcType=VARCHAR}, active=#{active,jdbcType=BOOLEAN},
+ scm_accounts=#{scmAccounts,jdbcType=VARCHAR},
+ salt=#{salt,jdbcType=VARCHAR}, crypted_password=#{cryptedPassword,jdbcType=BIGINT},
+ updated_at=#{updatedAt,jdbcType=BIGINT}
+ WHERE login = #{login}
+ </insert>
+
+</mapper>
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/SchemaMigrationMapper.xml b/sonar-db/src/main/resources/org/sonar/db/version/SchemaMigrationMapper.xml
new file mode 100644
index 00000000000..cde37378a00
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/version/SchemaMigrationMapper.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.version.SchemaMigrationMapper">
+
+ <select id="selectVersions" resultType="int">
+ select version from schema_migrations
+ </select>
+
+ <insert id="insert" parameterType="string" useGeneratedKeys="false">
+ insert into schema_migrations(version) values (#{version,jdbcType=VARCHAR})
+ </insert>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
new file mode 100644
index 00000000000..706947ab524
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
@@ -0,0 +1,348 @@
+-- All the rows inserted during Rails migrations. Rows inserted during server startup tasks (Java) are excluded : rules, profiles, metrics, ...
+
+INSERT INTO GROUPS(ID, NAME, DESCRIPTION, CREATED_AT, UPDATED_AT) VALUES (1, 'sonar-administrators', 'System administrators', '2011-09-26 22:27:51.0', '2011-09-26 22:27:51.0');
+INSERT INTO GROUPS(ID, NAME, DESCRIPTION, CREATED_AT, UPDATED_AT) VALUES (2, 'sonar-users', 'Any new users created will automatically join this group', '2011-09-26 22:27:51.0', '2011-09-26 22:27:51.0');
+ALTER TABLE GROUPS ALTER COLUMN ID RESTART WITH 3;
+
+INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (1, 1, null, 'admin');
+INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (2, 1, null, 'profileadmin');
+INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (3, 1, null, 'shareDashboard');
+INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (4, null, null, 'scan');
+INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (5, null, null, 'dryRunScan');
+INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (6, 1, null, 'provisioning');
+ALTER TABLE GROUP_ROLES ALTER COLUMN ID RESTART WITH 7;
+
+INSERT INTO GROUPS_USERS(USER_ID, GROUP_ID) VALUES (1, 1);
+INSERT INTO GROUPS_USERS(USER_ID, GROUP_ID) VALUES (1, 2);
+
+
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('2');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('10');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('11');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('14');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('35');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('48');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('49');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('53');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('54');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('59');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('61');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('62');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('79');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('80');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('86');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('87');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('88');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('93');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('95');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('111');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('115');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('118');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('119');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('120');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('131');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('132');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('133');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('134');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('135');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('136');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('137');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('138');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('139');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('140');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('141');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('142');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('151');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('160');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('162');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('163');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('165');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('166');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('167');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('168');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('169');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('170');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('180');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('190');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('191');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('200');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('201');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('202');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('203');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('210');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('211');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('212');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('213');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('214');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('215');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('216');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('217');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('220');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('221');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('222');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('230');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('231');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('232');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('233');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('234');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('235');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('236');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('237');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('238');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('239');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('240');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('241');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('250');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('251');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('252');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('254');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('255');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('256');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('257');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('258');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('259');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('260');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('261');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('262');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('263');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('280');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('281');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('282');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('283');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('284');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('285');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('286');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('287');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('300');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('301');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('302');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('303');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('304');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('305');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('306');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('320');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('321');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('330');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('331');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('332');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('333');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('334');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('335');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('350');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('351');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('352');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('353');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('354');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('355');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('356');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('358');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('359');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('360');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('361');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('362');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('363');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('370');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('379');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('380');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('381');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('382');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('383');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('387');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('388');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('391');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('392');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('394');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('397');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('398');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('399');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('400');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('401');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('402');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('403');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('404');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('405');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('406');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('410');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('411');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('412');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('413');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('414');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('415');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('416');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('417');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('418');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('419');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('430');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('431');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('432');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('433');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('434');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('440');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('441');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('442');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('443');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('444');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('460');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('461');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('462');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('463');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('464');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('465');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('466');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('467');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('480');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('481');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('482');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('483');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('484');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('485');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('486');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('488');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('489');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('490');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('492');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('493');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('494');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('495');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('496');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('497');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('498');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('510');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('511');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('512');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('513');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('514');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('515');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('516');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('517');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('518');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('519');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('520');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('521');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('522');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('523');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('524');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('525');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('526');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('530');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('531');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('532');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('533');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('534');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('535');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('536');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('537');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('539');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('540');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('541');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('542');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('544');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('545');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('546');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('547');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('548');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('549');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('551');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('552');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('553');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('554');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('555');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('556');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('580');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('581');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('582');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('583');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('584');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('600');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('601');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('603');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('604');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('605');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('702');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('703');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('704');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('705');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('706');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('707');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('708');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('709');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('710');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('711');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('712');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('713');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('714');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('715');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('716');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('717');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('718');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('719');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('720');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('721');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('750');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('752');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('753');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('754');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('755');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('756');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('757');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('758');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('759');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('760');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('761');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('762');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('763');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('764');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('765');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('766');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('768');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('769');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('770');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('771');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('772');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('773');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('775');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('776');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('777');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('778');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('779');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('780');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('781');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('782');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('783');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('784');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('785');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('786');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('787');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('788');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('789');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('790');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('791');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('792');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('793');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('794');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('795');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('796');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('900');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('901');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('902');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('903');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('904');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('905');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('906');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('907');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('908');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('912');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('913');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('915');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('916');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('917');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('918');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('919');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('920');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('921');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('922');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('923');
+
+INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482', null, null);
+ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
new file mode 100644
index 00000000000..d0ad5236787
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
@@ -0,0 +1,686 @@
+CREATE TABLE "GROUPS_USERS" (
+ "USER_ID" INTEGER,
+ "GROUP_ID" INTEGER
+);
+
+CREATE TABLE "CHARACTERISTICS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(100),
+ "NAME" VARCHAR(100),
+ "PARENT_ID" INTEGER,
+ "ROOT_ID" INTEGER,
+ "RULE_ID" INTEGER,
+ "FUNCTION_KEY" VARCHAR(100),
+ "FACTOR_VALUE" DOUBLE,
+ "FACTOR_UNIT" VARCHAR(100),
+ "OFFSET_VALUE" DOUBLE,
+ "OFFSET_UNIT" VARCHAR(100),
+ "CHARACTERISTIC_ORDER" INTEGER,
+ "ENABLED" BOOLEAN,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "RULES_PARAMETERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "RULE_ID" INTEGER NOT NULL,
+ "NAME" VARCHAR(128) NOT NULL,
+ "PARAM_TYPE" VARCHAR(512) NOT NULL,
+ "DEFAULT_VALUE" VARCHAR(4000),
+ "DESCRIPTION" VARCHAR(4000)
+);
+
+CREATE TABLE "RULES_PROFILES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(100) NOT NULL,
+ "LANGUAGE" VARCHAR(20),
+ "KEE" VARCHAR(255) NOT NULL,
+ "PARENT_KEE" VARCHAR(255),
+ "RULES_UPDATED_AT" VARCHAR(100),
+ "IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "PROJECT_QPROFILES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "PROFILE_KEY" VARCHAR(255) NOT NULL
+);
+
+CREATE TABLE "WIDGETS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "DASHBOARD_ID" INTEGER NOT NULL,
+ "WIDGET_KEY" VARCHAR(256) NOT NULL,
+ "NAME" VARCHAR(256),
+ "DESCRIPTION" VARCHAR(1000),
+ "COLUMN_INDEX" INTEGER,
+ "ROW_INDEX" INTEGER,
+ "CONFIGURED" BOOLEAN,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP,
+ "RESOURCE_ID" INTEGER
+);
+
+CREATE TABLE "GROUPS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(500),
+ "DESCRIPTION" VARCHAR(200),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "SNAPSHOTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "CREATED_AT" BIGINT,
+ "BUILD_DATE" BIGINT,
+ "PROJECT_ID" INTEGER NOT NULL,
+ "PARENT_SNAPSHOT_ID" INTEGER,
+ "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+ "PURGE_STATUS" INTEGER,
+ "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE,
+ "SCOPE" VARCHAR(3),
+ "QUALIFIER" VARCHAR(10),
+ "ROOT_SNAPSHOT_ID" INTEGER,
+ "VERSION" VARCHAR(500),
+ "PATH" VARCHAR(500),
+ "DEPTH" INTEGER,
+ "ROOT_PROJECT_ID" INTEGER,
+ "PERIOD1_MODE" VARCHAR(100),
+ "PERIOD1_PARAM" VARCHAR(100),
+ "PERIOD1_DATE" BIGINT,
+ "PERIOD2_MODE" VARCHAR(100),
+ "PERIOD2_PARAM" VARCHAR(100),
+ "PERIOD2_DATE" BIGINT,
+ "PERIOD3_MODE" VARCHAR(100),
+ "PERIOD3_PARAM" VARCHAR(100),
+ "PERIOD3_DATE" BIGINT,
+ "PERIOD4_MODE" VARCHAR(100),
+ "PERIOD4_PARAM" VARCHAR(100),
+ "PERIOD4_DATE" BIGINT,
+ "PERIOD5_MODE" VARCHAR(100),
+ "PERIOD5_PARAM" VARCHAR(100),
+ "PERIOD5_DATE" BIGINT
+);
+
+CREATE TABLE "SCHEMA_MIGRATIONS" (
+"VERSION" VARCHAR(256) NOT NULL
+);
+
+CREATE TABLE "GROUP_ROLES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "GROUP_ID" INTEGER,
+ "RESOURCE_ID" INTEGER,
+ "ROLE" VARCHAR(64) NOT NULL
+);
+
+CREATE TABLE "RULES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PLUGIN_RULE_KEY" VARCHAR(200) NOT NULL,
+ "PLUGIN_NAME" VARCHAR(255) NOT NULL,
+ "DESCRIPTION" VARCHAR(16777215),
+ "DESCRIPTION_FORMAT" VARCHAR(20),
+ "PRIORITY" INTEGER,
+ "IS_TEMPLATE" BOOLEAN DEFAULT FALSE,
+ "TEMPLATE_ID" INTEGER,
+ "PLUGIN_CONFIG_KEY" VARCHAR(500),
+ "NAME" VARCHAR(200),
+ "STATUS" VARCHAR(40),
+ "LANGUAGE" VARCHAR(20),
+ "NOTE_DATA" CLOB(2147483647),
+ "NOTE_USER_LOGIN" VARCHAR(255),
+ "NOTE_CREATED_AT" TIMESTAMP,
+ "NOTE_UPDATED_AT" TIMESTAMP,
+ "CHARACTERISTIC_ID" INTEGER,
+ "DEFAULT_CHARACTERISTIC_ID" INTEGER,
+ "REMEDIATION_FUNCTION" VARCHAR(20),
+ "DEFAULT_REMEDIATION_FUNCTION" VARCHAR(20),
+ "REMEDIATION_COEFF" VARCHAR(20),
+ "DEFAULT_REMEDIATION_COEFF" VARCHAR(20),
+ "REMEDIATION_OFFSET" VARCHAR(20),
+ "DEFAULT_REMEDIATION_OFFSET" VARCHAR(20),
+ "EFFORT_TO_FIX_DESCRIPTION" VARCHAR(4000),
+ "TAGS" VARCHAR(4000),
+ "SYSTEM_TAGS" VARCHAR(4000),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+
+CREATE TABLE "WIDGET_PROPERTIES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "WIDGET_ID" INTEGER NOT NULL,
+ "KEE" VARCHAR(100),
+ "TEXT_VALUE" VARCHAR(4000)
+);
+
+CREATE TABLE "EVENTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(400),
+ "COMPONENT_UUID" VARCHAR(50),
+ "SNAPSHOT_ID" INTEGER,
+ "CATEGORY" VARCHAR(50),
+ "EVENT_DATE" BIGINT NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "DESCRIPTION" VARCHAR(4000),
+ "EVENT_DATA" VARCHAR(4000)
+);
+
+CREATE TABLE "QUALITY_GATES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(100) NOT NULL,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP,
+);
+
+CREATE TABLE "QUALITY_GATE_CONDITIONS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "QGATE_ID" INTEGER,
+ "METRIC_ID" INTEGER,
+ "OPERATOR" VARCHAR(3),
+ "VALUE_ERROR" VARCHAR(64),
+ "VALUE_WARNING" VARCHAR(64),
+ "PERIOD" INTEGER,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP,
+);
+
+CREATE TABLE "PROPERTIES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROP_KEY" VARCHAR(512),
+ "RESOURCE_ID" INTEGER,
+ "TEXT_VALUE" CLOB(2147483647),
+ "USER_ID" INTEGER
+);
+
+CREATE TABLE "PROJECT_LINKS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "COMPONENT_UUID" VARCHAR(50),
+ "LINK_TYPE" VARCHAR(20),
+ "NAME" VARCHAR(128),
+ "HREF" VARCHAR(2048) NOT NULL
+);
+
+CREATE TABLE "DUPLICATIONS_INDEX" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_SNAPSHOT_ID" INTEGER NOT NULL,
+ "SNAPSHOT_ID" INTEGER NOT NULL,
+ "HASH" VARCHAR(50) NOT NULL,
+ "INDEX_IN_FILE" INTEGER NOT NULL,
+ "START_LINE" INTEGER NOT NULL,
+ "END_LINE" INTEGER NOT NULL
+);
+
+CREATE TABLE "PROJECT_MEASURES" (
+ "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "VALUE" DOUBLE,
+ "METRIC_ID" INTEGER NOT NULL,
+ "SNAPSHOT_ID" INTEGER,
+ "RULE_ID" INTEGER,
+ "RULES_CATEGORY_ID" INTEGER,
+ "TEXT_VALUE" VARCHAR(4000),
+ "TENDENCY" INTEGER,
+ "MEASURE_DATE" TIMESTAMP,
+ "PROJECT_ID" INTEGER,
+ "ALERT_STATUS" VARCHAR(5),
+ "ALERT_TEXT" VARCHAR(4000),
+ "URL" VARCHAR(2000),
+ "DESCRIPTION" VARCHAR(4000),
+ "RULE_PRIORITY" INTEGER,
+ "CHARACTERISTIC_ID" INTEGER,
+ "PERSON_ID" INTEGER,
+ "VARIATION_VALUE_1" DOUBLE,
+ "VARIATION_VALUE_2" DOUBLE,
+ "VARIATION_VALUE_3" DOUBLE,
+ "VARIATION_VALUE_4" DOUBLE,
+ "VARIATION_VALUE_5" DOUBLE,
+ "MEASURE_DATA" BINARY(167772150)
+);
+
+CREATE TABLE "PROJECTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(400),
+ "ROOT_ID" INTEGER,
+ "UUID" VARCHAR(50),
+ "PROJECT_UUID" VARCHAR(50),
+ "MODULE_UUID" VARCHAR(50),
+ "MODULE_UUID_PATH" VARCHAR(4000),
+ "NAME" VARCHAR(256),
+ "DESCRIPTION" VARCHAR(2000),
+ "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+ "SCOPE" VARCHAR(3),
+ "QUALIFIER" VARCHAR(10),
+ "DEPRECATED_KEE" VARCHAR(400),
+ "PATH" VARCHAR(2000),
+ "LANGUAGE" VARCHAR(20),
+ "COPY_RESOURCE_ID" INTEGER,
+ "LONG_NAME" VARCHAR(256),
+ "PERSON_ID" INTEGER,
+ "CREATED_AT" TIMESTAMP,
+ "AUTHORIZATION_UPDATED_AT" BIGINT
+);
+
+CREATE TABLE "MANUAL_MEASURES" (
+ "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "METRIC_ID" INTEGER NOT NULL,
+ "COMPONENT_UUID" VARCHAR(50),
+ "VALUE" DOUBLE,
+ "TEXT_VALUE" VARCHAR(4000),
+ "USER_LOGIN" VARCHAR(255),
+ "DESCRIPTION" VARCHAR(4000),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT
+);
+
+CREATE TABLE "ACTIVE_RULES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROFILE_ID" INTEGER NOT NULL,
+ "RULE_ID" INTEGER NOT NULL,
+ "FAILURE_LEVEL" INTEGER NOT NULL,
+ "INHERITANCE" VARCHAR(10),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "NOTIFICATIONS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "DATA" BLOB(167772150)
+);
+
+CREATE TABLE "USER_ROLES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "USER_ID" INTEGER,
+ "RESOURCE_ID" INTEGER,
+ "ROLE" VARCHAR(64) NOT NULL
+);
+
+CREATE TABLE "ACTIVE_DASHBOARDS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "DASHBOARD_ID" INTEGER NOT NULL,
+ "USER_ID" INTEGER,
+ "ORDER_INDEX" INTEGER
+);
+
+CREATE TABLE "ACTIVE_RULE_PARAMETERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "ACTIVE_RULE_ID" INTEGER NOT NULL,
+ "RULES_PARAMETER_ID" INTEGER NOT NULL,
+ "RULES_PARAMETER_KEY" VARCHAR(128),
+ "VALUE" VARCHAR(4000)
+);
+
+CREATE TABLE "USERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "LOGIN" VARCHAR(255),
+ "NAME" VARCHAR(200),
+ "EMAIL" VARCHAR(100),
+ "CRYPTED_PASSWORD" VARCHAR(40),
+ "SALT" VARCHAR(40),
+ "REMEMBER_TOKEN" VARCHAR(500),
+ "REMEMBER_TOKEN_EXPIRES_AT" TIMESTAMP,
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" VARCHAR(4000),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT
+);
+
+CREATE TABLE "DASHBOARDS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "USER_ID" INTEGER,
+ "NAME" VARCHAR(256),
+ "DESCRIPTION" VARCHAR(1000),
+ "COLUMN_LAYOUT" VARCHAR(20),
+ "SHARED" BOOLEAN,
+ "IS_GLOBAL" BOOLEAN,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "METRICS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(64) NOT NULL,
+ "DESCRIPTION" VARCHAR(255),
+ "DIRECTION" INTEGER NOT NULL DEFAULT 0,
+ "DOMAIN" VARCHAR(64),
+ "SHORT_NAME" VARCHAR(64),
+ "QUALITATIVE" BOOLEAN NOT NULL DEFAULT FALSE,
+ "VAL_TYPE" VARCHAR(8),
+ "USER_MANAGED" BOOLEAN DEFAULT FALSE,
+ "ENABLED" BOOLEAN DEFAULT TRUE,
+ "WORST_VALUE" DOUBLE,
+ "BEST_VALUE" DOUBLE,
+ "OPTIMIZED_BEST_VALUE" BOOLEAN,
+ "HIDDEN" BOOLEAN,
+ "DELETE_HISTORICAL_DATA" BOOLEAN
+);
+
+CREATE TABLE "LOADED_TEMPLATES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(200),
+ "TEMPLATE_TYPE" VARCHAR(15)
+);
+
+CREATE TABLE "RESOURCE_INDEX" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(400) NOT NULL,
+ "POSITION" INTEGER NOT NULL,
+ "NAME_SIZE" INTEGER NOT NULL,
+ "RESOURCE_ID" INTEGER NOT NULL,
+ "ROOT_PROJECT_ID" INTEGER NOT NULL,
+ "QUALIFIER" VARCHAR(10) NOT NULL
+);
+
+CREATE TABLE "ACTION_PLANS" (
+ "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(100),
+ "NAME" VARCHAR(200),
+ "DESCRIPTION" VARCHAR(1000),
+ "DEADLINE" TIMESTAMP,
+ "USER_LOGIN" VARCHAR(255),
+ "PROJECT_ID" INTEGER,
+ "STATUS" VARCHAR(10),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "AUTHORS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PERSON_ID" INTEGER,
+ "LOGIN" VARCHAR(100),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "SEMAPHORES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(4000),
+ "CHECKSUM" VARCHAR(200),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "LOCKED_AT" BIGINT
+);
+
+CREATE TABLE "MEASURE_FILTERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(100) NOT NULL,
+ "SHARED" BOOLEAN NOT NULL DEFAULT FALSE,
+ "USER_ID" INTEGER,
+ "DESCRIPTION" VARCHAR(4000),
+ "DATA" CLOB(2147483647),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "MEASURE_FILTER_FAVOURITES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "USER_ID" INTEGER NOT NULL,
+ "MEASURE_FILTER_ID" INTEGER NOT NULL,
+ "CREATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "ISSUES" (
+ "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(50) UNIQUE NOT NULL,
+ "COMPONENT_UUID" VARCHAR(50),
+ "PROJECT_UUID" VARCHAR(50),
+ "RULE_ID" INTEGER,
+ "SEVERITY" VARCHAR(10),
+ "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+ "MESSAGE" VARCHAR(4000),
+ "LINE" INTEGER,
+ "EFFORT_TO_FIX" DOUBLE,
+ "TECHNICAL_DEBT" INTEGER,
+ "STATUS" VARCHAR(20),
+ "RESOLUTION" VARCHAR(20),
+ "CHECKSUM" VARCHAR(1000),
+ "REPORTER" VARCHAR(255),
+ "ASSIGNEE" VARCHAR(255),
+ "AUTHOR_LOGIN" VARCHAR(255),
+ "ACTION_PLAN_KEY" VARCHAR(50) NULL,
+ "ISSUE_ATTRIBUTES" VARCHAR(4000),
+ "TAGS" VARCHAR(4000),
+ "ISSUE_CREATION_DATE" BIGINT,
+ "ISSUE_CLOSE_DATE" BIGINT,
+ "ISSUE_UPDATE_DATE" BIGINT,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT
+);
+
+CREATE TABLE "ISSUE_CHANGES" (
+ "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "KEE" VARCHAR(50),
+ "ISSUE_KEY" VARCHAR(50) NOT NULL,
+ "USER_LOGIN" VARCHAR(255),
+ "CHANGE_TYPE" VARCHAR(40),
+ "CHANGE_DATA" VARCHAR(16777215),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "ISSUE_CHANGE_CREATION_DATE" BIGINT
+);
+
+CREATE TABLE "ISSUE_FILTERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(100) NOT NULL,
+ "SHARED" BOOLEAN NOT NULL DEFAULT FALSE,
+ "USER_LOGIN" VARCHAR(255),
+ "DESCRIPTION" VARCHAR(4000),
+ "DATA" CLOB(2147483647),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "ISSUE_FILTER_FAVOURITES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "USER_LOGIN" VARCHAR(255) NOT NULL,
+ "ISSUE_FILTER_ID" INTEGER NOT NULL,
+ "CREATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "PERMISSION_TEMPLATES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(100) NOT NULL,
+ "KEE" VARCHAR(100) NOT NULL,
+ "DESCRIPTION" VARCHAR(4000),
+ "KEY_PATTERN" VARCHAR(500),
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "PERM_TEMPLATES_USERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "USER_ID" INTEGER NOT NULL,
+ "TEMPLATE_ID" INTEGER NOT NULL,
+ "PERMISSION_REFERENCE" VARCHAR(64) NOT NULL,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "PERM_TEMPLATES_GROUPS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "GROUP_ID" INTEGER,
+ "TEMPLATE_ID" INTEGER NOT NULL,
+ "PERMISSION_REFERENCE" VARCHAR(64) NOT NULL,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP
+);
+
+
+CREATE TABLE "ACTIVITIES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "LOG_KEY" VARCHAR(250),
+ "CREATED_AT" TIMESTAMP,
+ "USER_LOGIN" VARCHAR(30),
+ "LOG_TYPE" VARCHAR(250),
+ "LOG_ACTION" VARCHAR(250),
+ "LOG_MESSAGE" VARCHAR(250),
+ "DATA_FIELD" CLOB(2147483647)
+);
+
+CREATE TABLE "ANALYSIS_REPORTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_KEY" VARCHAR(400) NOT NULL,
+ "PROJECT_NAME" VARCHAR(256) NULL,
+ "REPORT_STATUS" VARCHAR(20) NOT NULL,
+ "UUID" VARCHAR(50) NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL,
+ "STARTED_AT" BIGINT,
+ "FINISHED_AT" BIGINT
+);
+
+CREATE TABLE "FILE_SOURCES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "FILE_UUID" VARCHAR(50) NOT NULL,
+ "LINE_HASHES" CLOB(2147483647),
+ "BINARY_DATA" BLOB(167772150),
+ "DATA_TYPE" VARCHAR(20),
+ "DATA_HASH" VARCHAR(50),
+ "SRC_HASH" VARCHAR(50),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+
+-- ----------------------------------------------
+-- DDL Statements for indexes
+-- ----------------------------------------------
+
+CREATE UNIQUE INDEX "LOG_KEY_INDEX" ON "ACTIVITIES" ("LOG_KEY");
+
+CREATE INDEX "GROUP_ROLES_RESOURCE" ON "GROUP_ROLES" ("RESOURCE_ID");
+
+CREATE INDEX "GROUP_ROLES_GROUP" ON "GROUP_ROLES" ("GROUP_ID");
+
+CREATE INDEX "USER_ROLES_RESOURCE" ON "USER_ROLES" ("RESOURCE_ID");
+
+CREATE INDEX "USER_ROLES_USER" ON "USER_ROLES" ("USER_ID");
+
+CREATE INDEX "DUPLICATIONS_INDEX_HASH" ON "DUPLICATIONS_INDEX" ("HASH");
+
+CREATE INDEX "DUPLICATIONS_INDEX_SID" ON "DUPLICATIONS_INDEX" ("SNAPSHOT_ID");
+
+CREATE INDEX "DUPLICATIONS_INDEX_PSID" ON "DUPLICATIONS_INDEX" ("PROJECT_SNAPSHOT_ID");
+
+CREATE INDEX "INDEX_GROUPS_USERS_ON_GROUP_ID" ON "GROUPS_USERS" ("GROUP_ID");
+
+CREATE INDEX "INDEX_GROUPS_USERS_ON_USER_ID" ON "GROUPS_USERS" ("USER_ID");
+
+CREATE UNIQUE INDEX "GROUPS_USERS_UNIQUE" ON "GROUPS_USERS" ("GROUP_ID", "USER_ID");
+
+CREATE INDEX "MEASURES_SID_METRIC" ON "PROJECT_MEASURES" ("SNAPSHOT_ID", "METRIC_ID");
+
+CREATE UNIQUE INDEX "METRICS_UNIQUE_NAME" ON "METRICS" ("NAME");
+
+CREATE INDEX "EVENTS_SNAPSHOT_ID" ON "EVENTS" ("SNAPSHOT_ID");
+
+CREATE INDEX "EVENTS_COMPONENT_UUID" ON "EVENTS" ("COMPONENT_UUID");
+
+CREATE INDEX "WIDGETS_WIDGETKEY" ON "WIDGETS" ("WIDGET_KEY");
+
+CREATE INDEX "WIDGETS_DASHBOARDS" ON "WIDGETS" ("DASHBOARD_ID");
+
+CREATE INDEX "SNAPSHOTS_QUALIFIER" ON "SNAPSHOTS" ("QUALIFIER");
+
+CREATE INDEX "SNAPSHOTS_ROOT" ON "SNAPSHOTS" ("ROOT_SNAPSHOT_ID");
+
+CREATE INDEX "SNAPSHOTS_PARENT" ON "SNAPSHOTS" ("PARENT_SNAPSHOT_ID");
+
+CREATE INDEX "SNAPSHOT_PROJECT_ID" ON "SNAPSHOTS" ("PROJECT_ID");
+
+CREATE INDEX "RULES_PARAMETERS_RULE_ID" ON "RULES_PARAMETERS" ("RULE_ID");
+
+CREATE INDEX "ACTIVE_DASHBOARDS_DASHBOARDID" ON "ACTIVE_DASHBOARDS" ("DASHBOARD_ID");
+
+CREATE INDEX "ACTIVE_DASHBOARDS_USERID" ON "ACTIVE_DASHBOARDS" ("USER_ID");
+
+CREATE INDEX "UNIQUE_SCHEMA_MIGRATIONS" ON "SCHEMA_MIGRATIONS" ("VERSION");
+
+CREATE INDEX "WIDGET_PROPERTIES_WIDGETS" ON "WIDGET_PROPERTIES" ("WIDGET_ID");
+
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");
+
+CREATE INDEX "MANUAL_MEASURES_COMPONENT_UUID" ON "MANUAL_MEASURES" ("COMPONENT_UUID");
+
+CREATE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE", "ENABLED");
+
+CREATE INDEX "PROJECTS_ROOT_ID" ON "PROJECTS" ("ROOT_ID");
+
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+
+CREATE INDEX "RESOURCE_INDEX_KEE" ON "RESOURCE_INDEX" ("KEE");
+
+CREATE INDEX "RESOURCE_INDEX_RID" ON "RESOURCE_INDEX" ("RESOURCE_ID");
+
+CREATE INDEX "INDEX_ACTION_PLANS_ON_PROJET_ID" ON "ACTION_PLANS" ("PROJECT_ID");
+
+CREATE UNIQUE INDEX "UNIQ_SEMAPHORE_CHECKSUMS" ON "SEMAPHORES" ("CHECKSUM");
+
+CREATE INDEX "SEMAPHORE_NAMES" ON "SEMAPHORES" ("NAME");
+
+CREATE UNIQUE INDEX "UNIQ_AUTHOR_LOGINS" ON "AUTHORS" ("LOGIN");
+
+CREATE INDEX "MEASURE_FILTERS_NAME" ON "MEASURE_FILTERS" ("NAME");
+
+CREATE INDEX "MEASURE_FILTER_FAVS_USERID" ON "MEASURE_FILTER_FAVOURITES" ("USER_ID");
+
+CREATE UNIQUE INDEX "ISSUES_KEE" ON "ISSUES" ("KEE");
+
+CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES" ("COMPONENT_UUID");
+
+CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES" ("PROJECT_UUID");
+
+CREATE INDEX "ISSUES_RULE_ID" ON "ISSUES" ("RULE_ID");
+
+CREATE INDEX "ISSUES_SEVERITY" ON "ISSUES" ("SEVERITY");
+
+CREATE INDEX "ISSUES_STATUS" ON "ISSUES" ("STATUS");
+
+CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES" ("RESOLUTION");
+
+CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES" ("ASSIGNEE");
+
+CREATE INDEX "ISSUES_ACTION_PLAN_KEY" ON "ISSUES" ("ACTION_PLAN_KEY");
+
+CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES" ("ISSUE_CREATION_DATE");
+
+CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES" ("UPDATED_AT");
+
+CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES" ("KEE");
+
+CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES" ("ISSUE_KEY");
+
+CREATE INDEX "ISSUE_FILTERS_NAME" ON "ISSUE_FILTERS" ("NAME");
+
+CREATE INDEX "ISSUE_FILTER_FAVS_USER" ON "ISSUE_FILTER_FAVOURITES" ("USER_LOGIN");
+
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
+
+CREATE INDEX "SNAPSHOTS_ROOT_PROJECT_ID" ON "SNAPSHOTS" ("ROOT_PROJECT_ID");
+
+CREATE INDEX "GROUP_ROLES_ROLE" ON "GROUP_ROLES" ("ROLE");
+
+CREATE UNIQUE INDEX "UNIQ_GROUP_ROLES" ON "GROUP_ROLES" ("GROUP_ID", "RESOURCE_ID", "ROLE");
+
+CREATE UNIQUE INDEX "RULES_REPO_KEY" ON "RULES" ("PLUGIN_NAME", "PLUGIN_RULE_KEY");
+
+CREATE INDEX "CHARACTERISTICS_ENABLED" ON "CHARACTERISTICS" ("ENABLED");
+
+CREATE UNIQUE INDEX "QUALITY_GATES_UNIQUE" ON "QUALITY_GATES" ("NAME");
+
+CREATE UNIQUE INDEX "ACTIVE_RULES_UNIQUE" ON "ACTIVE_RULES" ("PROFILE_ID","RULE_ID");
+
+CREATE UNIQUE INDEX "PROFILE_UNIQUE_KEY" ON "RULES_PROFILES" ("KEE");
+
+CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID");
+
+CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE_UNIQUE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE");
+
+CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT");
+
+CREATE UNIQUE INDEX "PROJECT_QPROFILES_UNIQUE" ON "PROJECT_QPROFILES" ("PROJECT_UUID", "PROFILE_KEY");
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/v44/Migration44Mapper.xml b/sonar-db/src/main/resources/org/sonar/db/version/v44/Migration44Mapper.xml
new file mode 100644
index 00000000000..30bcffddccb
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/version/v44/Migration44Mapper.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.version.v44.Migration44Mapper">
+
+ <select id="selectProfileMeasures" resultType="org.sonar.db.version.v44.ProfileMeasure">
+ select pm.id as id, pm.value as profileId, pm.snapshot_id as snapshotId
+ from project_measures pm
+ inner join metrics m on m.id=pm.metric_id and m.name='profile'
+ inner join snapshots s on s.islast=${_true} and pm.snapshot_id=s.id and s.scope='PRJ'
+ where pm.value is not null
+ </select>
+
+ <select id="selectProfileVersion" resultType="int" parameterType="long">
+ select pm.value from project_measures pm
+ inner join metrics m on m.id=pm.metric_id and m.name='profile_version'
+ inner join snapshots s on pm.snapshot_id=s.id
+ where pm.value is not null and s.id=#{id}
+ </select>
+
+ <select id="selectProfileById" resultType="org.sonar.db.version.v44.QProfileDto44" parameterType="int">
+ select id, kee, name, language
+ from rules_profiles
+ where id=#{id}
+ </select>
+
+ <select id="selectProfileVersionDate" resultType="date" parameterType="map">
+ select max(change_date) from active_rule_changes
+ where profile_id=#{profileId} and profile_version=#{profileVersion}
+ </select>
+
+ <update id="updateProfileMeasure" parameterType="map">
+ update project_measures
+ set text_value=#{json}, value=null
+ where id=#{measureId}
+ </update>
+
+ <delete id="deleteProfileMeasure" parameterType="long">
+ delete from project_measures where id=#{id}
+ </delete>
+
+ <select id="selectMeasuresOnDeletedQualityProfiles" resultType="long">
+ select pm.id from project_measures pm
+ inner join snapshots s on s.id=pm.snapshot_id and s.islast=${_true}
+ where
+ pm.metric_id=(select id from metrics where name='quality_profiles')
+ and pm.value is not null
+ </select>
+
+ <select id="selectAllProfiles" resultType="org.sonar.db.version.v44.QProfileDto44">
+ select id, kee, name, language from rules_profiles
+ </select>
+
+ <select id="selectProfileUpdatedAt" resultType="date" parameterType="int">
+ select max(change_date) from active_rule_changes
+ where profile_id=#{id}
+ </select>
+
+ <select id="selectProfileCreatedAt" resultType="date" parameterType="int">
+ select min(change_date) from active_rule_changes
+ where profile_id=#{id}
+ </select>
+
+ <update id="updateProfileDates" parameterType="map">
+ update rules_profiles
+ set created_at=#{createdAt}, updated_at=#{updatedAt}, rules_updated_at=#{rulesUpdatedAt}
+ where id=#{profileId}
+ </update>
+
+ <select id="selectActiveRuleChange" parameterType="Boolean"
+ resultType="org.sonar.db.version.v44.ChangeLog">
+ select
+ rule_change.id as id,
+ rule_change.change_date as createdAt,
+ users.login as userLogin,
+ rule_def.plugin_name as repository,
+ rule_def.plugin_rule_key as ruleKey,
+ profile.kee as profileKey,
+ rule_change.new_severity as severity ,
+ rule_def.name as ruleName,
+ rule_def_param.name as paramKey,
+ rule_param_change.new_value as paramValue
+ from active_rule_changes rule_change
+ left join users on users.name = rule_change.username
+ left join rules rule_def on rule_def.id = rule_change.rule_id
+ left join rules_profiles profile on profile.id = rule_change.profile_id
+ left join active_rule_param_changes rule_param_change on rule_param_change.active_rule_change_id = rule_change.id
+ left join rules_parameters rule_def_param on rule_param_change.rules_parameter_id = rule_def_param.id
+ WHERE
+ <choose>
+ <when test="enabled != null">
+ rule_change.enabled = #{enabled}
+ </when>
+ <otherwise>
+ rule_change.enabled is null
+ </otherwise>
+ </choose>
+ AND profile.name is not null
+ AND profile.language is not null
+ AND rule_def.plugin_name is not null
+ AND rule_def.plugin_name is not null
+ order by rule_change.id ASC
+ </select>
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/v45/Migration45Mapper.xml b/sonar-db/src/main/resources/org/sonar/db/version/v45/Migration45Mapper.xml
new file mode 100644
index 00000000000..0b741e6d396
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/version/v45/Migration45Mapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.version.v45.Migration45Mapper">
+
+</mapper>
+
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/v50/Migration50Mapper.xml b/sonar-db/src/main/resources/org/sonar/db/version/v50/Migration50Mapper.xml
new file mode 100644
index 00000000000..46fd146b117
--- /dev/null
+++ b/sonar-db/src/main/resources/org/sonar/db/version/v50/Migration50Mapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.version.v50.Migration50Mapper">
+
+</mapper>
+
diff --git a/sonar-db/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java b/sonar-db/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java
new file mode 100644
index 00000000000..233f99787b2
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/core/issue/ActionPlanStatsTest.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.issue;
+
+import java.util.Date;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Test;
+import org.sonar.api.issue.ActionPlan;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ActionPlanStatsTest {
+
+ @Test
+ public void test_over_due() throws Exception {
+ Date yesterday = DateUtils.addDays(new Date(), -1);
+ Date tomorrow = DateUtils.addDays(new Date(), 1);
+
+ assertThat(((ActionPlanStats) ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_OPEN).setDeadLine(tomorrow)).overDue()).isFalse();
+ assertThat(((ActionPlanStats) ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_OPEN).setDeadLine(yesterday)).overDue()).isTrue();
+ assertThat(((ActionPlanStats) ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_CLOSED).setDeadLine(tomorrow)).overDue()).isFalse();
+ assertThat(((ActionPlanStats) ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_CLOSED).setDeadLine(yesterday)).overDue()).isFalse();
+ assertThat(((ActionPlanStats) ActionPlanStats.create("Short term").setStatus(ActionPlan.STATUS_CLOSED)).overDue()).isFalse();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/core/timemachine/PeriodsTest.java b/sonar-db/src/test/java/org/sonar/core/timemachine/PeriodsTest.java
new file mode 100644
index 00000000000..ee5fbb87bb7
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/core/timemachine/PeriodsTest.java
@@ -0,0 +1,262 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.timemachine;
+
+import java.util.Locale;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.utils.System2;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class PeriodsTest {
+
+ private Periods periods;
+
+ private Snapshot snapshot;
+
+ private Settings settings;
+ private I18n i18n;
+
+ private int periodIndex;
+ private String param;
+
+ @Before
+ public void before() {
+ periodIndex = 1;
+ param = "10";
+
+ snapshot = mock(Snapshot.class);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ settings = new Settings();
+ i18n = mock(I18n.class);
+ periods = new Periods(settings, i18n);
+ }
+
+ @Test
+ public void label_of_duration_in_days() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_DAYS);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System2.INSTANCE.now());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("over_x_days_detailed"), isNull(String.class), eq(param), anyString());
+ }
+
+ @Test
+ public void abbreviation_of_duration_in_days() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_DAYS);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.abbreviation(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("over_x_days_detailed.short"), isNull(String.class), eq(param), anyString());
+ }
+
+ @Test
+ public void label_of_snapshot_version() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_VERSION);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_version_detailed"), isNull(String.class), eq(param), anyString());
+ }
+
+ @Test
+ public void abbreviation_of_snapshot_version() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_VERSION);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.abbreviation(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_version_detailed.short"), isNull(String.class), eq(param), anyString());
+ }
+
+ @Test
+ public void label_of_previous_analysis_with_date() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_analysis_detailed"), isNull(String.class), anyString());
+ }
+
+ @Test
+ public void label_of_previous_analysis_without_date() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(null);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_analysis"), isNull(String.class));
+ }
+
+ @Test
+ public void abbreviation_of_previous_analysis_with_date() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+
+ periods.abbreviation(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_analysis_detailed.short"), isNull(String.class), anyString());
+ }
+
+ @Test
+ public void abbreviation_of_previous_analysis_without_date() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(null);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.abbreviation(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_analysis.short"), isNull(String.class));
+ }
+
+ @Test
+ public void shouldReturnSnapshotLabelInModePreviousVersionWithParamNotNull() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_version_detailed"), isNull(String.class), eq(param));
+ }
+
+ @Test
+ public void label_of_previous_version_with_param_and_date() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_version_detailed"), isNull(String.class), eq(param), anyString());
+ }
+
+ @Test
+ public void shouldReturnSnapshotLabelInModePreviousVersionWithParamNull() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(null);
+
+ periods.label(snapshot, periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_version"), isNull(String.class));
+ }
+
+ @Test
+ public void shouldReturnSnapshotLabelInModeDate() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_DATE);
+ when(snapshot.getPeriodDateMs(periodIndex)).thenReturn(System.currentTimeMillis());
+
+ periods.label(snapshot, periodIndex);
+
+ verify(i18n).message(any(Locale.class), eq("since_x"), isNull(String.class), anyString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotSupportUnknownModeForSnapshotLabel() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn("Unknown mode");
+
+ periods.label(snapshot, periodIndex);
+ }
+
+ @Test
+ public void shouldReturnLabelInModeDays() {
+ int periodIndex = 1;
+ String days = "5";
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, days);
+
+ periods.label(periodIndex);
+ verify(i18n).message(any(Locale.class), eq("over_x_days"), isNull(String.class), eq(days));
+ }
+
+ @Test
+ public void shouldReturnLabelInModeVersion() {
+ int periodIndex = 1;
+ String version = "3.5";
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, version);
+
+ periods.label(periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_version"), isNull(String.class), eq(version));
+ }
+
+ @Test
+ public void shouldReturnLabelInModePreviousAnalysis() {
+ int periodIndex = 1;
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+
+ periods.label(periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_analysis"), isNull(String.class));
+ }
+
+ @Test
+ public void label_of_previous_version() {
+ int periodIndex = 1;
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+
+ periods.label(periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_version"), isNull(String.class));
+ }
+
+ @Test
+ public void abbreviation_of_previous_version() {
+ int periodIndex = 1;
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+
+ periods.abbreviation(periodIndex);
+ verify(i18n).message(any(Locale.class), eq("since_previous_version.short"), isNull(String.class));
+ }
+
+ @Test
+ public void label_of_date() {
+ int periodIndex = 1;
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, "2012-12-12");
+
+ periods.label(periodIndex);
+
+ verify(i18n).message(any(Locale.class), eq("since_x"), isNull(String.class), anyString());
+ }
+
+ @Test
+ public void abbreviation_of_date() {
+ int periodIndex = 1;
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, "2012-12-12");
+
+ periods.abbreviation(periodIndex);
+
+ verify(i18n).message(any(Locale.class), eq("since_x.short"), isNull(String.class), anyString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldNotSupportUnknownModeForLabel() {
+ int periodIndex = 1;
+ settings.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex, "");
+
+ periods.label(periodIndex);
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/core/user/DefaultUserFinderTest.java b/sonar-db/src/test/java/org/sonar/core/user/DefaultUserFinderTest.java
new file mode 100644
index 00000000000..2a97bef0a21
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/core/user/DefaultUserFinderTest.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.sonar.api.user.User;
+import org.sonar.api.user.UserQuery;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultUserFinderTest {
+ UserDao dao = mock(UserDao.class);
+ DefaultUserFinder finder = new DefaultUserFinder(dao);
+
+ @Test
+ public void findByLogin() {
+ UserDto dto = new UserDto().setLogin("david").setName("David").setEmail("dav@id.com");
+ when(dao.selectActiveUserByLogin("david")).thenReturn(dto);
+
+ assertThat(finder.findByLogin("david").name()).isEqualTo("David");
+ }
+
+ @Test
+ public void findByLogins() {
+ UserDto david = new UserDto().setLogin("david").setName("David").setEmail("dav@id.com");
+ UserDto john = new UserDto().setLogin("john").setName("John").setEmail("jo@hn.com");
+ when(dao.selectUsersByLogins(Arrays.asList("david", "john"))).thenReturn(Arrays.asList(david, john));
+
+ Collection<User> users = finder.findByLogins(Arrays.asList("david", "john"));
+ assertThat(users).hasSize(2);
+ for (User user : users) {
+ assertThat(user.login()).isIn("david", "john");
+ }
+ }
+
+ @Test
+ public void findByQuery() {
+ UserQuery query = UserQuery.builder().logins("simon").build();
+ finder.find(query);
+ verify(dao).selectUsers(query);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/core/user/DefaultUserTest.java b/sonar-db/src/test/java/org/sonar/core/user/DefaultUserTest.java
new file mode 100644
index 00000000000..6028a82dd3d
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/core/user/DefaultUserTest.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultUserTest {
+ @Test
+ public void test_object_methods() throws Exception {
+ DefaultUser john = new DefaultUser().setLogin("john").setName("John");
+ DefaultUser eric = new DefaultUser().setLogin("eric").setName("Eric");
+
+ assertThat(john).isEqualTo(john);
+ assertThat(john).isNotEqualTo(eric);
+ assertThat(john.hashCode()).isEqualTo(john.hashCode());
+ assertThat(john.toString()).contains("login=john").contains("name=John");
+ }
+
+ @Test
+ public void test_email() {
+ DefaultUser user = new DefaultUser();
+ assertThat(user.email()).isNull();
+
+ user.setEmail("");
+ assertThat(user.email()).isNull();
+
+ user.setEmail(" ");
+ assertThat(user.email()).isNull();
+
+ user.setEmail("s@b.com");
+ assertThat(user.email()).isEqualTo("s@b.com");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/core/user/DeprecatedUserFinderTest.java b/sonar-db/src/test/java/org/sonar/core/user/DeprecatedUserFinderTest.java
new file mode 100644
index 00000000000..0d978a6124a
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/core/user/DeprecatedUserFinderTest.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.api.database.model.User;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDao;
+import org.sonar.test.DbTests;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+@Category(DbTests.class)
+public class DeprecatedUserFinderTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ @BeforeClass
+ public static void init() {
+ dbTester.prepareDbUnit(DeprecatedUserFinderTest.class, "fixture.xml");
+ }
+
+ @Test
+ public void shouldFindUserByLogin() {
+ DeprecatedUserFinder finder = new DeprecatedUserFinder(new UserDao(dbTester.myBatis(), mock(System2.class)));
+ User user = finder.findByLogin("simon");
+ assertThat(user.getId(), is(1));
+ assertThat(user.getLogin(), is("simon"));
+ assertThat(user.getName(), is("Simon Brandhof"));
+ assertThat(user.getEmail(), is("simon.brandhof@sonarsource.com"));
+
+ user = finder.findByLogin("godin");
+ assertThat(user.getId(), is(2));
+ assertThat(user.getLogin(), is("godin"));
+ assertThat(user.getName(), is("Evgeny Mandrikov"));
+ assertThat(user.getEmail(), is("evgeny.mandrikov@sonarsource.com"));
+
+ user = finder.findByLogin("user");
+ assertThat(user, nullValue());
+ }
+
+ @Test
+ public void shouldFindUserById() {
+ DeprecatedUserFinder finder = new DeprecatedUserFinder(new UserDao(dbTester.myBatis(), mock(System2.class)));
+ User user = finder.findById(1);
+ assertThat(user.getId(), is(1));
+ assertThat(user.getLogin(), is("simon"));
+ assertThat(user.getName(), is("Simon Brandhof"));
+ assertThat(user.getEmail(), is("simon.brandhof@sonarsource.com"));
+
+ user = finder.findById(2);
+ assertThat(user.getId(), is(2));
+ assertThat(user.getLogin(), is("godin"));
+ assertThat(user.getName(), is("Evgeny Mandrikov"));
+ assertThat(user.getEmail(), is("evgeny.mandrikov@sonarsource.com"));
+
+ user = finder.findById(3);
+ assertThat(user, nullValue());
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/AbstractDaoTestCase.java b/sonar-db/src/test/java/org/sonar/db/AbstractDaoTestCase.java
new file mode 100644
index 00000000000..756a3437595
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/AbstractDaoTestCase.java
@@ -0,0 +1,308 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.google.common.collect.Maps;
+import com.google.common.io.Closeables;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.dbunit.Assertion;
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.database.DatabaseConfig;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.CompositeDataSet;
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.ReplacementDataSet;
+import org.dbunit.dataset.filter.DefaultColumnFilter;
+import org.dbunit.dataset.xml.FlatXmlDataSet;
+import org.dbunit.ext.mssql.InsertIdentityOperation;
+import org.dbunit.ext.mysql.MySqlMetadataHandler;
+import org.dbunit.operation.DatabaseOperation;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.db.deprecated.NullQueue;
+import org.sonar.db.dialect.MySql;
+import org.sonar.test.DbTests;
+
+import static org.junit.Assert.fail;
+
+/**
+ * @deprecated use an instance of {@link DbTester instead} instead,
+ * and do no forget to annotated the test class with {@link org.sonar.test.DbTests}.
+ */
+@Category(DbTests.class)
+@Deprecated
+public abstract class AbstractDaoTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractDaoTestCase.class);
+ private static Database database;
+ private static DatabaseCommands databaseCommands;
+ private static MyBatis myBatis;
+ private static String login;
+
+ private IDatabaseTester databaseTester;
+
+ @BeforeClass
+ public static void startDatabase() throws Exception {
+ if (database == null) {
+ Settings settings = new Settings().setProperties(Maps.fromProperties(System.getProperties()));
+ if (settings.hasKey("orchestrator.configUrl")) {
+ loadOrchestratorSettings(settings);
+ }
+ login = settings.getString("sonar.jdbc.username");
+ for (String key : settings.getKeysStartingWith("sonar.jdbc")) {
+ LOG.info(key + ": " + settings.getString(key));
+ }
+ boolean hasDialect = settings.hasKey("sonar.jdbc.dialect");
+ if (hasDialect) {
+ database = new DefaultDatabase(settings);
+ } else {
+ database = new H2Database("test", true);
+ }
+ database.start();
+ LOG.info("Test Database: " + database);
+ databaseCommands = DatabaseCommands.forDialect(database.getDialect());
+
+ myBatis = new MyBatis(database, new NullQueue());
+ myBatis.start();
+ }
+ }
+
+ /**
+ * Orchestrator is the name of a SonarSource close-source library for database and integration testing.
+ */
+ private static void loadOrchestratorSettings(Settings settings) throws URISyntaxException, IOException {
+ String url = settings.getString("orchestrator.configUrl");
+ URI uri = new URI(url);
+ InputStream input = null;
+ try {
+ if (url.startsWith("file:")) {
+ File file = new File(uri);
+ input = FileUtils.openInputStream(file);
+ } else {
+ HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
+ int responseCode = connection.getResponseCode();
+ if (responseCode >= 400) {
+ throw new IllegalStateException("Fail to request: " + uri + ". Status code=" + responseCode);
+ }
+
+ input = connection.getInputStream();
+
+ }
+ Properties props = new Properties();
+ props.load(input);
+ settings.addProperties(props);
+ for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) {
+ String interpolatedValue = StrSubstitutor.replace(entry.getValue(), System.getenv(), "${", "}");
+ settings.setProperty(entry.getKey(), interpolatedValue);
+ }
+ } finally {
+ IOUtils.closeQuietly(input);
+ }
+ }
+
+ private static RuntimeException translateException(String msg, Exception cause) {
+ RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage()));
+ runtimeException.setStackTrace(cause.getStackTrace());
+ return runtimeException;
+ }
+
+ @Before
+ public void startDbUnit() throws Exception {
+ databaseCommands.truncateDatabase(database.getDataSource());
+ databaseTester = new DataSourceDatabaseTester(database.getDataSource(), databaseCommands.useLoginAsSchema() ? login : null);
+ }
+
+ protected MyBatis getMyBatis() {
+ return myBatis;
+ }
+
+ protected Database getDatabase() {
+ return database;
+ }
+
+ protected void setupData(String... testNames) {
+ InputStream[] streams = new InputStream[testNames.length];
+ try {
+ for (int i = 0; i < testNames.length; i++) {
+ String className = getClass().getName();
+ className = String.format("/%s/%s.xml", className.replace(".", "/"), testNames[i]);
+ streams[i] = getClass().getResourceAsStream(className);
+ if (streams[i] == null) {
+ throw new RuntimeException("Test not found :" + className);
+ }
+ }
+
+ setupData(streams);
+ databaseCommands.resetPrimaryKeys(database.getDataSource());
+ } catch (SQLException e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ for (InputStream stream : streams) {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+ }
+
+ private void setupData(InputStream... dataSetStream) {
+ IDatabaseConnection connection = openDbUnitConnection();
+ try {
+ IDataSet[] dataSets = new IDataSet[dataSetStream.length];
+ for (int i = 0; i < dataSetStream.length; i++) {
+ dataSets[i] = getData(dataSetStream[i]);
+ }
+ databaseTester.setDataSet(new CompositeDataSet(dataSets));
+ new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, databaseTester.getDataSet());
+ } catch (Exception e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ closeDbUnitConnection(connection);
+ }
+ }
+
+ protected void checkTables(String testName, String... tables) {
+ checkTables(testName, new String[0], tables);
+ }
+
+ protected void checkTables(String testName, String[] excludedColumnNames, String... tables) {
+ IDatabaseConnection connection = openDbUnitConnection();
+ try {
+ IDataSet dataSet = connection.createDataSet();
+ IDataSet expectedDataSet = getExpectedData(testName);
+ for (String table : tables) {
+ ITable filteredTable = DefaultColumnFilter.excludedColumnsTable(dataSet.getTable(table), excludedColumnNames);
+ ITable filteredExpectedTable = DefaultColumnFilter.excludedColumnsTable(expectedDataSet.getTable(table), excludedColumnNames);
+ Assertion.assertEquals(filteredExpectedTable, filteredTable);
+ }
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ } catch (SQLException e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeDbUnitConnection(connection);
+ }
+ }
+
+ protected void checkTable(String testName, String table, String... columns) {
+ IDatabaseConnection connection = openDbUnitConnection();
+ try {
+ IDataSet dataSet = connection.createDataSet();
+ IDataSet expectedDataSet = getExpectedData(testName);
+ ITable filteredTable = DefaultColumnFilter.includedColumnsTable(dataSet.getTable(table), columns);
+ ITable filteredExpectedTable = DefaultColumnFilter.includedColumnsTable(expectedDataSet.getTable(table), columns);
+ Assertion.assertEquals(filteredExpectedTable, filteredTable);
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ } catch (SQLException e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeDbUnitConnection(connection);
+ }
+ }
+
+ protected void assertEmptyTables(String... emptyTables) {
+ IDatabaseConnection connection = openDbUnitConnection();
+ try {
+ IDataSet dataSet = connection.createDataSet();
+ for (String table : emptyTables) {
+ try {
+ Assert.assertEquals("Table " + table + " not empty.", 0, dataSet.getTable(table).getRowCount());
+ } catch (DataSetException e) {
+ throw translateException("Error while checking results", e);
+ }
+ }
+ } catch (SQLException e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeDbUnitConnection(connection);
+ }
+ }
+
+ private IDatabaseConnection openDbUnitConnection() {
+ try {
+ IDatabaseConnection connection = databaseTester.getConnection();
+ connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, databaseCommands.getDbUnitFactory());
+ connection.getConfig().setFeature(DatabaseConfig.FEATURE_QUALIFIED_TABLE_NAMES, false);
+ connection.getConfig().setFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, true);
+ if (MySql.ID.equals(database.getDialect().getId())) {
+ connection.getConfig().setProperty(DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES, false);
+ connection.getConfig().setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER, new MySqlMetadataHandler());
+ }
+ return connection;
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to open dbunit connection", e);
+ }
+ }
+
+ private void closeDbUnitConnection(IDatabaseConnection c) {
+ try {
+ c.close();
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to close dbunit connection", e);
+ }
+ }
+
+ private IDataSet getExpectedData(String testName) {
+ String className = getClass().getName();
+ String fileName = String.format("/%s/%s-result.xml", className.replace('.', '/'), testName);
+ InputStream in = getClass().getResourceAsStream(fileName);
+ try {
+ return getData(in);
+ } finally {
+ Closeables.closeQuietly(in);
+ }
+ }
+
+ private IDataSet getData(InputStream stream) {
+ try {
+ ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream));
+ dataSet.addReplacementObject("[null]", null);
+ dataSet.addReplacementObject("[false]", Boolean.FALSE);
+ dataSet.addReplacementObject("[true]", Boolean.TRUE);
+ return dataSet;
+ } catch (Exception e) {
+ throw translateException("Could not read the dataset stream", e);
+ }
+ }
+
+ protected Connection getConnection() throws SQLException {
+ return database.getDataSource().getConnection();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/BatchSessionTest.java b/sonar-db/src/test/java/org/sonar/db/BatchSessionTest.java
new file mode 100644
index 00000000000..dc6999e61a1
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/BatchSessionTest.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Test;
+import org.sonar.db.deprecated.ClusterAction;
+import org.sonar.db.deprecated.WorkQueue;
+
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class BatchSessionTest {
+ @Test
+ public void shouldCommitWhenReachingBatchSize() {
+ SqlSession mybatisSession = mock(SqlSession.class);
+ WorkQueue<?> queue = mock(WorkQueue.class);
+ BatchSession session = new BatchSession(queue, mybatisSession, 10);
+
+ for (int i = 0; i < 9; i++) {
+ session.insert("id" + i);
+ verify(mybatisSession).insert("id" + i);
+ verify(mybatisSession, never()).commit();
+ verify(mybatisSession, never()).commit(anyBoolean());
+ }
+ session.insert("id9");
+ verify(mybatisSession).commit();
+ session.close();
+ }
+
+ @Test
+ public void shouldCommitWhenReachingBatchSizeWithoutCommits() {
+ SqlSession mybatisSession = mock(SqlSession.class);
+ WorkQueue<?> queue = mock(WorkQueue.class);
+ BatchSession session = new BatchSession(queue, mybatisSession, 10);
+
+ ClusterAction action = new ClusterAction() {
+ @Override
+ public Object call() throws Exception {
+ return null;
+ }
+ };
+
+ for (int i = 0; i < 9; i++) {
+ session.enqueue(action);
+ verify(mybatisSession, never()).commit();
+ verify(mybatisSession, never()).commit(anyBoolean());
+ }
+ session.enqueue(action);
+ verify(mybatisSession).commit();
+ session.close();
+ }
+
+ @Test
+ public void shouldResetCounterAfterCommit() {
+ SqlSession mybatisSession = mock(SqlSession.class);
+ WorkQueue<?> queue = mock(WorkQueue.class);
+ BatchSession session = new BatchSession(queue, mybatisSession, 10);
+
+ for (int i = 0; i < 35; i++) {
+ session.insert("id" + i);
+ }
+ verify(mybatisSession, times(3)).commit();
+ session.close();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/DaoUtilsTest.java b/sonar-db/src/test/java/org/sonar/db/DaoUtilsTest.java
new file mode 100644
index 00000000000..24987271dfb
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/DaoUtilsTest.java
@@ -0,0 +1,85 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+public class DaoUtilsTest {
+
+ @Test
+ public void list_all_dao_classes() {
+ List<Class> daoClasses = DaoUtils.getDaoClasses();
+
+ assertThat(daoClasses).isNotEmpty();
+ }
+
+ @Test
+ public void repeatCondition() {
+ assertThat(DaoUtils.repeatCondition("uuid=?", 1, "or")).isEqualTo("uuid=?");
+ assertThat(DaoUtils.repeatCondition("uuid=?", 3, "or")).isEqualTo("uuid=? or uuid=? or uuid=?");
+ }
+
+ @Test
+ public void execute_large_inputs() {
+ List<Integer> inputs = newArrayList();
+ List<String> expectedOutputs = newArrayList();
+ for (int i = 0; i < 2010; i++) {
+ inputs.add(i);
+ expectedOutputs.add(Integer.toString(i));
+ }
+
+ List<String> outputs = DaoUtils.executeLargeInputs(inputs, new Function<List<Integer>, List<String>>() {
+ @Override
+ public List<String> apply(List<Integer> input) {
+ // Check that each partition is only done on 1000 elements max
+ assertThat(input.size()).isLessThanOrEqualTo(1000);
+ return newArrayList(Iterables.transform(input, new Function<Integer, String>() {
+ @Override
+ public String apply(Integer input) {
+ return Integer.toString(input);
+ }
+ }));
+ }
+ });
+
+ assertThat(outputs).isEqualTo(expectedOutputs);
+ }
+
+ @Test
+ public void execute_large_inputs_on_empty_list() {
+ List<String> outputs = DaoUtils.executeLargeInputs(Collections.<Integer>emptyList(), new Function<List<Integer>, List<String>>() {
+ @Override
+ public List<String> apply(List<Integer> input) {
+ fail("No partition should be made on empty list");
+ return Collections.emptyList();
+ }
+ });
+
+ assertThat(outputs).isEmpty();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/DatabaseCommands.java b/sonar-db/src/test/java/org/sonar/db/DatabaseCommands.java
new file mode 100644
index 00000000000..626933d4aef
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/DatabaseCommands.java
@@ -0,0 +1,211 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.List;
+import javax.sql.DataSource;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.lang.StringUtils;
+import org.dbunit.dataset.datatype.DefaultDataTypeFactory;
+import org.dbunit.dataset.datatype.IDataTypeFactory;
+import org.dbunit.dataset.datatype.ToleratedDeltaMap;
+import org.dbunit.ext.h2.H2DataTypeFactory;
+import org.dbunit.ext.mssql.MsSqlDataTypeFactory;
+import org.dbunit.ext.mysql.MySqlDataTypeFactory;
+import org.dbunit.ext.oracle.Oracle10DataTypeFactory;
+import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.MsSql;
+import org.sonar.db.dialect.MySql;
+import org.sonar.db.dialect.Oracle;
+import org.sonar.db.dialect.PostgreSql;
+import org.sonar.db.version.DatabaseVersion;
+
+public abstract class DatabaseCommands {
+ private final IDataTypeFactory dbUnitFactory;
+
+ private DatabaseCommands(DefaultDataTypeFactory dbUnitFactory) {
+ this.dbUnitFactory = dbUnitFactory;
+
+ // Hack for MsSQL failure in IssueMapperTest.
+ // All the Double fields should be listed here.
+ dbUnitFactory.addToleratedDelta(new ToleratedDeltaMap.ToleratedDelta("issues", "effort_to_fix", 0.0001));
+ }
+
+ public final IDataTypeFactory getDbUnitFactory() {
+ return dbUnitFactory;
+ }
+
+ abstract List<String> resetSequenceSql(String table, int minSequenceValue);
+
+ String truncateSql(String table) {
+ return "TRUNCATE TABLE " + table;
+ }
+
+ boolean useLoginAsSchema() {
+ return false;
+ }
+
+ public static DatabaseCommands forDialect(Dialect dialect) {
+ DatabaseCommands command = ImmutableMap.of(
+ org.sonar.db.dialect.H2.ID, H2,
+ MsSql.ID, MSSQL,
+ MySql.ID, MYSQL,
+ Oracle.ID, ORACLE,
+ PostgreSql.ID, POSTGRESQL).get(dialect.getId());
+
+ return Preconditions.checkNotNull(command, "Unknown database: " + dialect);
+ }
+
+ private static final DatabaseCommands H2 = new DatabaseCommands(new H2DataTypeFactory()) {
+ @Override
+ List<String> resetSequenceSql(String table, int minSequenceValue) {
+ return Arrays.asList("ALTER TABLE " + table + " ALTER COLUMN ID RESTART WITH " + minSequenceValue);
+ }
+ };
+
+ private static final DatabaseCommands POSTGRESQL = new DatabaseCommands(new PostgresqlDataTypeFactory()) {
+ @Override
+ List<String> resetSequenceSql(String table, int minSequenceValue) {
+ return Arrays.asList("ALTER SEQUENCE " + table + "_id_seq RESTART WITH " + minSequenceValue);
+ }
+ };
+
+ private static final DatabaseCommands ORACLE = new DatabaseCommands(new Oracle10DataTypeFactory()) {
+ @Override
+ List<String> resetSequenceSql(String table, int minSequenceValue) {
+ String sequence = StringUtils.upperCase(table) + "_SEQ";
+ return Arrays.asList(
+ "DROP SEQUENCE " + sequence,
+ "CREATE SEQUENCE " + sequence + " INCREMENT BY 1 MINVALUE 1 START WITH " + minSequenceValue);
+ }
+
+ @Override
+ String truncateSql(String table) {
+ return "TRUNCATE TABLE " + table + " REUSE STORAGE";
+ }
+
+ @Override
+ boolean useLoginAsSchema() {
+ return true;
+ }
+ };
+
+ private static final DatabaseCommands MSSQL = new DatabaseCommands(new MsSqlDataTypeFactory()) {
+ @Override
+ public void resetPrimaryKeys(DataSource dataSource) {
+ }
+
+ @Override
+ List<String> resetSequenceSql(String table, int minSequenceValue) {
+ return null;
+ }
+
+ @Override
+ protected boolean shouldTruncate(Connection connection, String table) throws SQLException {
+ // truncate all tables on mssql, else unexpected errors in some tests
+ return true;
+ }
+ };
+
+ private static final DatabaseCommands MYSQL = new DatabaseCommands(new MySqlDataTypeFactory()) {
+ @Override
+ public void resetPrimaryKeys(DataSource dataSource) {
+ }
+
+ @Override
+ List<String> resetSequenceSql(String table, int minSequenceValue) {
+ return null;
+ }
+ };
+
+ public void truncateDatabase(DataSource dataSource) throws SQLException {
+ Connection connection = dataSource.getConnection();
+ Statement statement = null;
+ try {
+ connection.setAutoCommit(false);
+ statement = connection.createStatement();
+ for (String table : DatabaseVersion.TABLES) {
+ try {
+ if (shouldTruncate(connection, table)) {
+ statement.executeUpdate(truncateSql(table));
+ connection.commit();
+ }
+ } catch (Exception e) {
+ connection.rollback();
+ throw new IllegalStateException("Fail to truncate table " + table, e);
+ }
+ }
+ } finally {
+ DbUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(statement);
+ }
+ }
+
+ protected boolean shouldTruncate(Connection connection, String table) throws SQLException {
+ Statement stmt = connection.createStatement();
+ ResultSet rs = null;
+ try {
+ rs = stmt.executeQuery("select count(*) from " + table);
+ if (rs.next()) {
+ return rs.getInt(1) > 0;
+ }
+
+ } catch (SQLException ignored) {
+ // probably because table does not exist. That's the case with H2 tests.
+ } finally {
+ DbUtils.closeQuietly(rs);
+ DbUtils.closeQuietly(stmt);
+ }
+ return false;
+ }
+
+ public void resetPrimaryKeys(DataSource dataSource) throws SQLException {
+ Connection connection = dataSource.getConnection();
+ connection.setAutoCommit(false);
+
+ Statement statement = connection.createStatement();
+ for (String table : DatabaseVersion.TABLES) {
+ try {
+ ResultSet result = statement.executeQuery("SELECT CASE WHEN MAX(ID) IS NULL THEN 1 ELSE MAX(ID)+1 END FROM " + table);
+ result.next();
+ int maxId = result.getInt(1);
+ result.close();
+
+ for (String resetCommand : resetSequenceSql(table, maxId)) {
+ statement.executeUpdate(resetCommand);
+ }
+ connection.commit();
+ } catch (Exception e) {
+ connection.rollback(); // this table has no primary key
+ }
+ }
+
+ statement.close();
+ connection.close();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/DatabaseUtilsTest.java b/sonar-db/src/test/java/org/sonar/db/DatabaseUtilsTest.java
new file mode 100644
index 00000000000..933e931ac03
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/DatabaseUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import org.junit.Test;
+import org.sonar.db.dialect.Oracle;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class DatabaseUtilsTest extends AbstractDaoTestCase {
+
+ @Test
+ public void should_close_connection() throws SQLException {
+ Connection connection = getConnection();
+ assertThat(isClosed(connection)).isFalse();
+
+ DatabaseUtils.closeQuietly(connection);
+ assertThat(isClosed(connection)).isTrue();
+ }
+
+ @Test
+ public void should_support_null_connection() {
+ DatabaseUtils.closeQuietly((Connection) null);
+ // no failure
+ }
+
+ @Test
+ public void should_close_statement_and_resultset() throws SQLException {
+ Connection connection = getConnection();
+ try {
+ PreparedStatement statement = connection.prepareStatement(selectDual());
+ ResultSet rs = statement.executeQuery();
+
+ DatabaseUtils.closeQuietly(rs);
+ DatabaseUtils.closeQuietly(statement);
+
+ assertThat(isClosed(statement)).isTrue();
+ assertThat(isClosed(rs)).isTrue();
+ } finally {
+ DatabaseUtils.closeQuietly(connection);
+ }
+ }
+
+ @Test
+ public void should_not_fail_on_connection_errors() throws SQLException {
+ Connection connection = mock(Connection.class);
+ doThrow(new SQLException()).when(connection).close();
+
+ DatabaseUtils.closeQuietly(connection);
+
+ // no failure
+ verify(connection).close(); // just to be sure
+ }
+
+ @Test
+ public void should_not_fail_on_statement_errors() throws SQLException {
+ Statement statement = mock(Statement.class);
+ doThrow(new SQLException()).when(statement).close();
+
+ DatabaseUtils.closeQuietly(statement);
+
+ // no failure
+ verify(statement).close(); // just to be sure
+ }
+
+ @Test
+ public void should_not_fail_on_resulset_errors() throws SQLException {
+ ResultSet rs = mock(ResultSet.class);
+ doThrow(new SQLException()).when(rs).close();
+
+ DatabaseUtils.closeQuietly(rs);
+
+ // no failure
+ verify(rs).close(); // just to be sure
+ }
+
+ /**
+ * Connection.isClosed() has been introduced in java 1.6
+ */
+ private boolean isClosed(Connection c) {
+ try {
+ c.createStatement().execute(selectDual());
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ /**
+ * Statement.isClosed() has been introduced in java 1.6
+ */
+ private boolean isClosed(Statement s) {
+ try {
+ s.execute("SELECT 1");
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ /**
+ * ResultSet.isClosed() has been introduced in java 1.6
+ */
+ private boolean isClosed(ResultSet rs) {
+ try {
+ rs.next();
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ private String selectDual() {
+ String sql = "SELECT 1";
+ if (Oracle.ID.equals(getDatabase().getDialect().getId())) {
+ sql = "SELECT 1 FROM DUAL";
+ }
+ return sql;
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/DbTester.java b/sonar-db/src/test/java/org/sonar/db/DbTester.java
new file mode 100644
index 00000000000..ba428c5cbe8
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/DbTester.java
@@ -0,0 +1,453 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.dbunit.Assertion;
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.assertion.DiffCollectingFailureHandler;
+import org.dbunit.assertion.Difference;
+import org.dbunit.database.DatabaseConfig;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.CompositeDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.ReplacementDataSet;
+import org.dbunit.dataset.filter.DefaultColumnFilter;
+import org.dbunit.dataset.xml.FlatXmlDataSet;
+import org.dbunit.ext.mssql.InsertIdentityOperation;
+import org.dbunit.operation.DatabaseOperation;
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.db.deprecated.NullQueue;
+import org.sonar.db.dialect.Dialect;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * This class should be call using @ClassRule in order to create the schema once (ft @Rule is used
+ * the schema will be recreated before each test).
+ * Data will be truncated each time you call prepareDbUnit().
+ * <p/>
+ * File using {@link DbTester} must be annotated with {@link org.sonar.test.DbTests} so
+ * that they can be executed on all supported DBs (Oracle, MySQL, ...).
+ */
+public class DbTester extends ExternalResource {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DbTester.class);
+
+ private Database db;
+ private DatabaseCommands commands;
+ private IDatabaseTester tester;
+ private MyBatis myBatis;
+ private String schemaPath = null;
+
+ public DbTester schema(Class baseClass, String filename) {
+ String path = StringUtils.replaceChars(baseClass.getCanonicalName(), '.', '/');
+ schemaPath = path + "/" + filename;
+ return this;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ Settings settings = new Settings().setProperties(Maps.fromProperties(System.getProperties()));
+ if (settings.hasKey("orchestrator.configUrl")) {
+ loadOrchestratorSettings(settings);
+ }
+ String login = settings.getString("sonar.jdbc.username");
+ for (String key : settings.getKeysStartingWith("sonar.jdbc")) {
+ LOG.info(key + ": " + settings.getString(key));
+ }
+ String dialect = settings.getString("sonar.jdbc.dialect");
+ if (dialect != null && !"h2".equals(dialect)) {
+ db = new DefaultDatabase(settings);
+ } else {
+ db = new H2Database("h2Tests" + DigestUtils.md5Hex(StringUtils.defaultString(schemaPath)), schemaPath == null);
+ }
+ db.start();
+ if (schemaPath != null) {
+ // will fail if not H2
+ if (db.getDialect().getId().equals("h2")) {
+ ((H2Database) db).executeScript(schemaPath);
+ } else {
+ db.stop();
+ throw new AssumptionViolatedException("Test disabled because it supports only H2");
+ }
+ }
+ LOG.info("Test Database: " + db);
+
+ commands = DatabaseCommands.forDialect(db.getDialect());
+ tester = new DataSourceDatabaseTester(db.getDataSource(), commands.useLoginAsSchema() ? login : null);
+
+ myBatis = new MyBatis(db, new NullQueue());
+ myBatis.start();
+
+ truncateTables();
+ }
+
+ public void truncateTables() {
+ try {
+ commands.truncateDatabase(db.getDataSource());
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to truncate db tables", e);
+ }
+ }
+
+ @Override
+ protected void after() {
+ db.stop();
+ db = null;
+ myBatis = null;
+ }
+
+ public Database database() {
+ return db;
+ }
+
+ public Dialect dialect() {
+ return db.getDialect();
+ }
+
+ public MyBatis myBatis() {
+ return myBatis;
+ }
+
+ public Connection openConnection() throws SQLException {
+ return db.getDataSource().getConnection();
+ }
+
+ public void executeUpdateSql(String sql) {
+ try (Connection connection = openConnection()) {
+ new QueryRunner().update(connection, sql);
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to execute sql: " + sql);
+ }
+ }
+
+ /**
+ * Returns the number of rows in the table. Example:
+ * <pre>int issues = countTable("issues")</pre>
+ */
+ public int countRowsOfTable(String tableName) {
+ Preconditions.checkArgument(StringUtils.containsNone(tableName, " "), "Parameter must be the name of a table. Got " + tableName);
+ return countSql("select count(*) from " + tableName);
+ }
+
+ /**
+ * Executes a SQL request starting with "SELECT COUNT(something) FROM", for example:
+ * <pre>int OpenIssues = countSql("select count('id') from issues where status is not null")</pre>
+ */
+ public int countSql(String sql) {
+ Preconditions.checkArgument(StringUtils.contains(sql, "count("),
+ "Parameter must be a SQL request containing 'count(x)' function. Got " + sql);
+ try (
+ Connection connection = openConnection();
+ PreparedStatement stmt = connection.prepareStatement(sql);
+ ResultSet rs = stmt.executeQuery()) {
+ if (rs.next()) {
+ return rs.getInt(1);
+ }
+ throw new IllegalStateException("No results for " + sql);
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to execute sql: " + sql, e);
+ }
+ }
+
+ public List<Map<String, Object>> select(String selectSql) {
+ try (
+ Connection connection = openConnection();
+ PreparedStatement stmt = connection.prepareStatement(selectSql);
+ ResultSet rs = stmt.executeQuery()) {
+ return getHashMap(rs);
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to execute sql: " + selectSql, e);
+ }
+ }
+
+ public Map<String, Object> selectFirst(String selectSql) {
+ List<Map<String, Object>> rows = select(selectSql);
+ if (rows.isEmpty()) {
+ throw new IllegalStateException("No results for " + selectSql);
+ } else if (rows.size() > 1) {
+ throw new IllegalStateException("Too many results for " + selectSql);
+ }
+ return rows.get(0);
+ }
+
+ private static List<Map<String, Object>> getHashMap(ResultSet resultSet) throws Exception {
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int colCount = metaData.getColumnCount();
+ List<Map<String, Object>> rows = newArrayList();
+ while (resultSet.next()) {
+ Map<String, Object> columns = newHashMap();
+ for (int i = 1; i <= colCount; i++) {
+ Object value = resultSet.getObject(i);
+ if (value instanceof Clob) {
+ Clob clob = (Clob) value;
+ value = IOUtils.toString((clob.getAsciiStream()));
+ doClobFree(clob);
+ } else if (value instanceof BigDecimal) {
+ // In Oracle, INTEGER types are mapped as BigDecimal
+ BigDecimal bgValue = ((BigDecimal) value);
+ if (bgValue.scale() == 0) {
+ value = bgValue.longValue();
+ } else {
+ value = bgValue.doubleValue();
+ }
+ } else if (value instanceof Integer) {
+ // To be consistent, all INTEGER types are mapped as Long
+ value = ((Integer) value).longValue();
+ }
+ columns.put(metaData.getColumnLabel(i), value);
+ }
+ rows.add(columns);
+ }
+ return rows;
+ }
+
+ public void prepareDbUnit(Class testClass, String... testNames) {
+ InputStream[] streams = new InputStream[testNames.length];
+ try {
+ // Purge previous data
+ commands.truncateDatabase(db.getDataSource());
+
+ for (int i = 0; i < testNames.length; i++) {
+ String path = "/" + testClass.getName().replace('.', '/') + "/" + testNames[i];
+ streams[i] = testClass.getResourceAsStream(path);
+ if (streams[i] == null) {
+ throw new IllegalStateException("DbUnit file not found: " + path);
+ }
+ }
+
+ prepareDbUnit(streams);
+ commands.resetPrimaryKeys(db.getDataSource());
+ } catch (SQLException e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ for (InputStream stream : streams) {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+ }
+
+ private void prepareDbUnit(InputStream... dataSetStream) {
+ IDatabaseConnection connection = null;
+ try {
+ IDataSet[] dataSets = new IDataSet[dataSetStream.length];
+ for (int i = 0; i < dataSetStream.length; i++) {
+ dataSets[i] = dbUnitDataSet(dataSetStream[i]);
+ }
+ tester.setDataSet(new CompositeDataSet(dataSets));
+ connection = dbUnitConnection();
+ new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, tester.getDataSet());
+ } catch (Exception e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ public void assertDbUnit(Class testClass, String filename, String... tables) {
+ assertDbUnit(testClass, filename, new String[0], tables);
+ }
+
+ public void assertDbUnit(Class testClass, String filename, String[] excludedColumnNames, String... tables) {
+ IDatabaseConnection connection = null;
+ try {
+ connection = dbUnitConnection();
+
+ IDataSet dataSet = connection.createDataSet();
+ String path = "/" + testClass.getName().replace('.', '/') + "/" + filename;
+ InputStream inputStream = testClass.getResourceAsStream(path);
+ if (inputStream == null) {
+ throw new IllegalStateException(String.format("File '%s' does not exist", path));
+ }
+ IDataSet expectedDataSet = dbUnitDataSet(inputStream);
+ for (String table : tables) {
+ DiffCollectingFailureHandler diffHandler = new DiffCollectingFailureHandler();
+
+ ITable filteredTable = DefaultColumnFilter.excludedColumnsTable(dataSet.getTable(table), excludedColumnNames);
+ ITable filteredExpectedTable = DefaultColumnFilter.excludedColumnsTable(expectedDataSet.getTable(table), excludedColumnNames);
+ Assertion.assertEquals(filteredExpectedTable, filteredTable, diffHandler);
+ // Evaluate the differences and ignore some column values
+ List diffList = diffHandler.getDiffList();
+ for (Object o : diffList) {
+ Difference diff = (Difference) o;
+ if (!"[ignore]".equals(diff.getExpectedValue())) {
+ throw new DatabaseUnitException(diff.toString());
+ }
+ }
+ }
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ } catch (Exception e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize) {
+ try (Connection connection = openConnection();
+ PreparedStatement stmt = connection.prepareStatement("select * from " + table);
+ ResultSet res = stmt.executeQuery()) {
+ Integer columnIndex = getColumnIndex(res, column);
+ if (columnIndex == null) {
+ fail("The column '" + column + "' does not exist");
+ }
+
+ assertThat(res.getMetaData().getColumnType(columnIndex)).isEqualTo(expectedType);
+ if (expectedSize != null) {
+ assertThat(res.getMetaData().getColumnDisplaySize(columnIndex)).isEqualTo(expectedSize);
+ }
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to check column");
+ }
+ }
+
+ @CheckForNull
+ private Integer getColumnIndex(ResultSet res, String column) {
+ try {
+ ResultSetMetaData meta = res.getMetaData();
+ int numCol = meta.getColumnCount();
+ for (int i = 1; i < numCol + 1; i++) {
+ if (meta.getColumnLabel(i).toLowerCase().equals(column.toLowerCase())) {
+ return i;
+ }
+ }
+ return null;
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to get column idnex");
+ }
+ }
+
+ private IDataSet dbUnitDataSet(InputStream stream) {
+ try {
+ ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream));
+ dataSet.addReplacementObject("[null]", null);
+ dataSet.addReplacementObject("[false]", Boolean.FALSE);
+ dataSet.addReplacementObject("[true]", Boolean.TRUE);
+
+ return dataSet;
+ } catch (Exception e) {
+ throw translateException("Could not read the dataset stream", e);
+ }
+ }
+
+ private IDatabaseConnection dbUnitConnection() {
+ try {
+ IDatabaseConnection connection = tester.getConnection();
+ connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, commands.getDbUnitFactory());
+ return connection;
+ } catch (Exception e) {
+ throw translateException("Error while getting connection", e);
+ }
+ }
+
+ private void closeQuietly(IDatabaseConnection connection) {
+ try {
+ if (connection != null) {
+ connection.close();
+ }
+ } catch (SQLException e) {
+ // ignore
+ }
+ }
+
+ private static RuntimeException translateException(String msg, Exception cause) {
+ RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage()));
+ runtimeException.setStackTrace(cause.getStackTrace());
+ return runtimeException;
+ }
+
+ private void loadOrchestratorSettings(Settings settings) throws URISyntaxException, IOException {
+ String url = settings.getString("orchestrator.configUrl");
+ URI uri = new URI(url);
+ InputStream input = null;
+ try {
+ if (url.startsWith("file:")) {
+ File file = new File(uri);
+ input = FileUtils.openInputStream(file);
+ } else {
+ HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
+ int responseCode = connection.getResponseCode();
+ if (responseCode >= 400) {
+ throw new IllegalStateException("Fail to request: " + uri + ". Status code=" + responseCode);
+ }
+
+ input = connection.getInputStream();
+
+ }
+ Properties props = new Properties();
+ props.load(input);
+ settings.addProperties(props);
+ for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) {
+ String interpolatedValue = StrSubstitutor.replace(entry.getValue(), System.getenv(), "${", "}");
+ settings.setProperty(entry.getKey(), interpolatedValue);
+ }
+ } finally {
+ IOUtils.closeQuietly(input);
+ }
+ }
+
+ private static void doClobFree(Clob clob) throws SQLException {
+ try {
+ clob.free();
+ } catch (AbstractMethodError e) {
+ // JTS driver do not implement free() as it's using JDBC 3.0
+ }
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/DdlUtilsTest.java b/sonar-db/src/test/java/org/sonar/db/DdlUtilsTest.java
new file mode 100644
index 00000000000..ddc7764b350
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/DdlUtilsTest.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import org.assertj.core.api.Assertions;
+import org.h2.Driver;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DdlUtilsTest {
+
+ @Test
+ public void shouldSupportOnlyH2() {
+ Assertions.assertThat(DdlUtils.supportsDialect("h2")).isTrue();
+ assertThat(DdlUtils.supportsDialect("mysql")).isFalse();
+ assertThat(DdlUtils.supportsDialect("oracle")).isFalse();
+ assertThat(DdlUtils.supportsDialect("mssql")).isFalse();
+ }
+
+ @Test
+ public void shouldCreateSchema() throws SQLException {
+ DriverManager.registerDriver(new Driver());
+ Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test");
+ DdlUtils.createSchema(connection, "h2");
+
+ int tableCount = countTables(connection);
+
+ connection.close();
+ assertThat(tableCount).isGreaterThan(30);
+ }
+
+ static int countTables(Connection connection) throws SQLException {
+ int count = 0;
+ ResultSet resultSet = connection.getMetaData().getTables("", null, null, new String[] {"TABLE"});
+ while (resultSet.next()) {
+ count++;
+ }
+ resultSet.close();
+ return count;
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java b/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java
new file mode 100644
index 00000000000..8fda6636030
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java
@@ -0,0 +1,126 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.util.Properties;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.db.dialect.PostgreSql;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultDatabaseTest {
+
+ @Test
+ public void shouldLoadDefaultValues() {
+ DefaultDatabase db = new DefaultDatabase(new Settings());
+ db.initSettings();
+
+ Properties props = db.getProperties();
+ assertThat(props.getProperty("sonar.jdbc.username")).isEqualTo("sonar");
+ assertThat(props.getProperty("sonar.jdbc.password")).isEqualTo("sonar");
+ assertThat(props.getProperty("sonar.jdbc.url")).isEqualTo("jdbc:h2:tcp://localhost/sonar");
+ assertThat(props.getProperty("sonar.jdbc.driverClassName")).isEqualTo("org.h2.Driver");
+ assertThat(db.toString()).isEqualTo("Database[jdbc:h2:tcp://localhost/sonar]");
+ }
+
+ @Test
+ public void shouldSupportDeprecatedUserProperty() {
+ Settings settings = new Settings();
+ settings.setProperty("sonar.jdbc.user", "me");
+
+ DefaultDatabase db = new DefaultDatabase(settings);
+ db.initSettings();
+ Properties props = db.getProperties();
+
+ assertThat(props.getProperty("sonar.jdbc.username")).isEqualTo("me");
+ }
+
+ @Test
+ public void shouldExtractCommonsDbcpProperties() {
+ Properties props = new Properties();
+ props.setProperty("sonar.jdbc.driverClassName", "my.Driver");
+ props.setProperty("sonar.jdbc.username", "me");
+ props.setProperty("sonar.jdbc.maxActive", "5");
+
+ Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props);
+
+ assertThat(commonsDbcpProps.getProperty("username")).isEqualTo("me");
+ assertThat(commonsDbcpProps.getProperty("driverClassName")).isEqualTo("my.Driver");
+ assertThat(commonsDbcpProps.getProperty("maxActive")).isEqualTo("5");
+ }
+
+ @Test
+ public void shouldCompleteProperties() {
+ Settings settings = new Settings();
+
+ DefaultDatabase db = new DefaultDatabase(settings) {
+ @Override
+ protected void doCompleteProperties(Properties properties) {
+ properties.setProperty("sonar.jdbc.maxActive", "2");
+ }
+ };
+ db.initSettings();
+
+ Properties props = db.getProperties();
+
+ assertThat(props.getProperty("sonar.jdbc.maxActive")).isEqualTo("2");
+ }
+
+ @Test
+ public void shouldStart() {
+ Settings settings = new Settings();
+ settings.setProperty("sonar.jdbc.url", "jdbc:h2:mem:sonar");
+ settings.setProperty("sonar.jdbc.driverClassName", "org.h2.Driver");
+ settings.setProperty("sonar.jdbc.username", "sonar");
+ settings.setProperty("sonar.jdbc.password", "sonar");
+ settings.setProperty("sonar.jdbc.maxActive", "1");
+
+ DefaultDatabase db = new DefaultDatabase(settings);
+ db.start();
+ db.stop();
+
+ assertThat(db.getDialect().getId()).isEqualTo("h2");
+ assertThat(((BasicDataSource) db.getDataSource()).getMaxActive()).isEqualTo(1);
+ }
+
+ @Test
+ public void shouldGuessDialectFromUrl() {
+ Settings settings = new Settings();
+ settings.setProperty("sonar.jdbc.url", "jdbc:postgresql://localhost/sonar");
+
+ DefaultDatabase database = new DefaultDatabase(settings);
+ database.initSettings();
+
+ assertThat(database.getDialect().getId()).isEqualTo(PostgreSql.ID);
+ }
+
+ @Test
+ public void shouldGuessDefaultDriver() {
+ Settings settings = new Settings();
+ settings.setProperty("sonar.jdbc.url", "jdbc:postgresql://localhost/sonar");
+
+ DefaultDatabase database = new DefaultDatabase(settings);
+ database.initSettings();
+
+ assertThat(database.getProperties().getProperty("sonar.jdbc.driverClassName")).isEqualTo("org.postgresql.Driver");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/H2Database.java b/sonar-db/src/test/java/org/sonar/db/H2Database.java
new file mode 100644
index 00000000000..688211e31b2
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/H2Database.java
@@ -0,0 +1,115 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbutils.DbUtils;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.H2;
+
+/**
+ * H2 in-memory database, used for unit tests only.
+ *
+ * @since 3.2
+ */
+public class H2Database implements Database {
+ private final String name;
+ private final boolean createSchema;
+ private BasicDataSource datasource;
+
+ /**
+ * IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest}
+ */
+ public H2Database(String name, boolean createSchema) {
+ this.name = name;
+ this.createSchema = createSchema;
+ }
+
+ @Override
+ public void start() {
+ startDatabase();
+ if (createSchema) {
+ createSchema();
+ }
+ }
+
+ private void startDatabase() {
+ try {
+ datasource = new BasicDataSource();
+ datasource.setDriverClassName("org.h2.Driver");
+ datasource.setUsername("sonar");
+ datasource.setPassword("sonar");
+ datasource.setUrl("jdbc:h2:mem:" + name);
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to start H2", e);
+ }
+ }
+
+ private void createSchema() {
+ Connection connection = null;
+ try {
+ connection = datasource.getConnection();
+ DdlUtils.createSchema(connection, "h2");
+
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to create schema", e);
+ } finally {
+ DbUtils.closeQuietly(connection);
+ }
+ }
+
+ public void executeScript(String classloaderPath) {
+ Connection connection = null;
+ try {
+ connection = datasource.getConnection();
+ DdlUtils.executeScript(connection, classloaderPath);
+
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to execute script: " + classloaderPath, e);
+ } finally {
+ DbUtils.closeQuietly(connection);
+ }
+ }
+
+ @Override
+ public void stop() {
+ try {
+ datasource.close();
+ } catch (SQLException e) {
+ // Ignore error
+ }
+ }
+
+ public DataSource getDataSource() {
+ return datasource;
+ }
+
+ public Dialect getDialect() {
+ return new H2();
+ }
+
+ @Override
+ public String toString() {
+ return "H2 Database[" + name + "]";
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/H2DatabaseTest.java b/sonar-db/src/test/java/org/sonar/db/H2DatabaseTest.java
new file mode 100644
index 00000000000..363fcc6619b
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/H2DatabaseTest.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class H2DatabaseTest {
+ H2Database db = new H2Database("sonar2", true);
+
+ @Before
+ public void startDb() {
+ db.start();
+ }
+
+ @After
+ public void stopDb() {
+ db.stop();
+ }
+
+ @Test
+ public void shouldExecuteDdlAtStartup() throws SQLException {
+ Connection connection = db.getDataSource().getConnection();
+ int tableCount = DdlUtilsTest.countTables(connection);
+ connection.close();
+
+ assertThat(tableCount).isGreaterThan(30);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/IsAliveMapperTest.java b/sonar-db/src/test/java/org/sonar/db/IsAliveMapperTest.java
new file mode 100644
index 00000000000..02b9c6b4427
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/IsAliveMapperTest.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class IsAliveMapperTest {
+
+ @ClassRule
+ public static DbTester dbTester = new DbTester();
+
+ DbSession session;
+ IsAliveMapper underTest;
+
+ @Before
+ public void setUp() {
+ session = dbTester.myBatis().openSession(false);
+ underTest = session.getMapper(IsAliveMapper.class);
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void isAlive_works_for_current_vendors() {
+ assertThat(underTest.isAlive()).isEqualTo(IsAliveMapper.IS_ALIVE_RETURNED_VALUE);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/MyBatisTest.java b/sonar-db/src/test/java/org/sonar/db/MyBatisTest.java
new file mode 100644
index 00000000000..194346fe0a0
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/MyBatisTest.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db;
+
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+import org.hamcrest.core.Is;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.sonar.db.deprecated.WorkQueue;
+import org.sonar.db.rule.RuleMapper;
+
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MyBatisTest {
+ private static H2Database database;
+ private WorkQueue<?> queue = mock(WorkQueue.class);
+
+ @BeforeClass
+ public static void start() {
+ database = new H2Database("sonar2", true);
+ database.start();
+ }
+
+ @AfterClass
+ public static void stop() {
+ database.stop();
+ }
+
+ @Test
+ public void shouldConfigureMyBatis() {
+ MyBatis myBatis = new MyBatis(database, queue);
+ myBatis.start();
+
+ Configuration conf = myBatis.getSessionFactory().getConfiguration();
+ assertThat(conf.isUseGeneratedKeys(), Is.is(true));
+ assertThat(conf.hasMapper(RuleMapper.class), Is.is(true));
+ assertThat(conf.isLazyLoadingEnabled(), Is.is(false));
+ }
+
+ @Test
+ public void shouldOpenBatchSession() {
+ MyBatis myBatis = new MyBatis(database, queue);
+ myBatis.start();
+
+ SqlSession session = myBatis.openBatchSession();
+ try {
+ assertThat(session.getConnection(), notNullValue());
+ assertThat(session.getMapper(RuleMapper.class), notNullValue());
+ } finally {
+ session.close();
+ }
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentDtoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentDtoTest.java
new file mode 100644
index 00000000000..c1dc490f695
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentDtoTest.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentDtoTest {
+
+ @Test
+ public void setters_and_getters() {
+ ComponentDto componentDto = new ComponentDto()
+ .setId(1L)
+ .setKey("org.struts:struts-core:src/org/struts/RequestContext.java")
+ .setDeprecatedKey("org.struts:struts-core:src/org/struts/RequestContext.java")
+ .setName("RequestContext.java")
+ .setLongName("org.struts.RequestContext")
+ .setQualifier("FIL")
+ .setScope("FIL")
+ .setLanguage("java")
+ .setDescription("desc")
+ .setPath("src/org/struts/RequestContext.java")
+ .setCopyResourceId(5L)
+ .setParentProjectId(3L)
+ .setAuthorizationUpdatedAt(123456789L);
+
+ assertThat(componentDto.getId()).isEqualTo(1L);
+ assertThat(componentDto.key()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java");
+ assertThat(componentDto.deprecatedKey()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java");
+ assertThat(componentDto.name()).isEqualTo("RequestContext.java");
+ assertThat(componentDto.longName()).isEqualTo("org.struts.RequestContext");
+ assertThat(componentDto.qualifier()).isEqualTo("FIL");
+ assertThat(componentDto.scope()).isEqualTo("FIL");
+ assertThat(componentDto.path()).isEqualTo("src/org/struts/RequestContext.java");
+ assertThat(componentDto.language()).isEqualTo("java");
+ assertThat(componentDto.description()).isEqualTo("desc");
+ assertThat(componentDto.parentProjectId()).isEqualTo(3L);
+ assertThat(componentDto.getCopyResourceId()).isEqualTo(5L);
+ assertThat(componentDto.getAuthorizationUpdatedAt()).isEqualTo(123456789L);
+ }
+
+ @Test
+ public void equals_and_hashcode() {
+ ComponentDto dto = new ComponentDto().setId(1L);
+ ComponentDto dtoWithSameId = new ComponentDto().setId(1L);
+ ComponentDto dtoWithDifferentId = new ComponentDto().setId(2L);
+
+ assertThat(dto).isEqualTo(dto);
+ assertThat(dto).isEqualTo(dtoWithSameId);
+ assertThat(dto).isNotEqualTo(dtoWithDifferentId);
+
+ assertThat(dto.hashCode()).isEqualTo(dto.hashCode());
+ assertThat(dto.hashCode()).isEqualTo(dtoWithSameId.hashCode());
+ assertThat(dto.hashCode()).isNotEqualTo(dtoWithDifferentId.hashCode());
+ }
+
+ @Test
+ public void toString_does_not_fail_if_empty() {
+ ComponentDto dto = new ComponentDto();
+ assertThat(dto.toString()).isNotEmpty();
+ }
+
+ @Test
+ public void is_root_project() {
+ assertThat(new ComponentDto().setModuleUuid("ABCD").isRootProject()).isFalse();
+ assertThat(new ComponentDto().setModuleUuid("ABCD").setScope(Scopes.DIRECTORY).isRootProject()).isFalse();
+ assertThat(new ComponentDto().setModuleUuid(null).setScope(Scopes.PROJECT).setQualifier(Qualifiers.PROJECT).isRootProject()).isTrue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentLinkDtoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentLinkDtoTest.java
new file mode 100644
index 00000000000..165a3ea08f2
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentLinkDtoTest.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentLinkDtoTest {
+
+ @Test
+ public void test_getters_and_setters() throws Exception {
+ ComponentLinkDto dto = new ComponentLinkDto()
+ .setId(1L)
+ .setComponentUuid("ABCD")
+ .setType("homepage")
+ .setName("Home")
+ .setHref("http://www.sonarqube.org");
+
+ assertThat(dto.getId()).isEqualTo(1L);
+ assertThat(dto.getComponentUuid()).isEqualTo("ABCD");
+ assertThat(dto.getType()).isEqualTo("homepage");
+ assertThat(dto.getName()).isEqualTo("Home");
+ assertThat(dto.getHref()).isEqualTo("http://www.sonarqube.org");
+ }
+
+ @Test
+ public void test_provided_types() throws Exception {
+ assertThat(ComponentLinkDto.PROVIDED_TYPES).hasSize(5);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ResourceDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ResourceDaoTest.java
new file mode 100644
index 00000000000..a6c809330f1
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/ResourceDaoTest.java
@@ -0,0 +1,470 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.session.SqlSession;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.component.Component;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ResourceDaoTest extends AbstractDaoTestCase {
+
+ private DbSession session;
+
+ private ResourceDao dao;
+ private System2 system2;
+
+ @Before
+ public void createDao() {
+ session = getMyBatis().openSession(false);
+ system2 = mock(System2.class);
+ when(system2.now()).thenReturn(1_500_000_000_000L);
+ dao = new ResourceDao(getMyBatis(), system2);
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void testDescendantProjects_do_not_include_self() {
+ setupData("fixture");
+
+ List<ResourceDto> resources = dao.getDescendantProjects(1L);
+
+ assertThat(resources).extracting("id").containsOnly(2L);
+ }
+
+ @Test
+ public void testDescendantProjects_id_not_found() {
+ setupData("fixture");
+
+ List<ResourceDto> resources = dao.getDescendantProjects(33333L);
+
+ assertThat(resources).isEmpty();
+ }
+
+ @Test
+ public void get_resource_by_id() {
+ setupData("fixture");
+
+ ResourceDto resource = dao.getResource(1L);
+
+ assertThat(resource.getUuid()).isEqualTo("ABCD");
+ assertThat(resource.getProjectUuid()).isEqualTo("ABCD");
+ assertThat(resource.getPath()).isNull();
+ assertThat(resource.getName()).isEqualTo("Struts");
+ assertThat(resource.getLongName()).isEqualTo("Apache Struts");
+ assertThat(resource.getScope()).isEqualTo("PRJ");
+ assertThat(resource.getDescription()).isEqualTo("the description");
+ assertThat(resource.getLanguage()).isEqualTo("java");
+ assertThat(resource.isEnabled()).isTrue();
+ assertThat(resource.getAuthorizationUpdatedAt()).isNotNull();
+ assertThat(resource.getCreatedAt()).isNotNull();
+ }
+
+ @Test
+ public void get_resource_by_uuid() {
+ setupData("fixture");
+
+ ResourceDto resource = dao.getResource("ABCD");
+
+ assertThat(resource.getUuid()).isEqualTo("ABCD");
+ assertThat(resource.getProjectUuid()).isEqualTo("ABCD");
+ assertThat(resource.getPath()).isNull();
+ assertThat(resource.getName()).isEqualTo("Struts");
+ assertThat(resource.getLongName()).isEqualTo("Apache Struts");
+ assertThat(resource.getScope()).isEqualTo("PRJ");
+ assertThat(resource.getDescription()).isEqualTo("the description");
+ assertThat(resource.getLanguage()).isEqualTo("java");
+ assertThat(resource.isEnabled()).isTrue();
+ assertThat(resource.getAuthorizationUpdatedAt()).isNotNull();
+ assertThat(resource.getCreatedAt()).isNotNull();
+ }
+
+ @Test
+ public void get_resource_path_and_module_key() {
+ setupData("fixture");
+
+ ResourceDto dir = dao.getResource(3L);
+ assertThat(dir.getPath()).isEqualTo("src/org/struts");
+
+ ResourceDto file = dao.getResource(4L);
+ assertThat(file.getPath()).isEqualTo("src/org/struts/RequestContext.java");
+ }
+
+ @Test
+ public void get_uuid() {
+ setupData("fixture");
+
+ ResourceDto file = dao.getResource(4L);
+ assertThat(file.getUuid()).isEqualTo("DEFG");
+ assertThat(file.getProjectUuid()).isEqualTo("ABCD");
+ assertThat(file.getModuleUuid()).isEqualTo("BCDE");
+ assertThat(file.getModuleUuidPath()).isEqualTo(".ABCD.BCDE.");
+ }
+
+ @Test
+ public void getResource_not_found() {
+ setupData("fixture");
+
+ assertThat(dao.getResource(987654321L)).isNull();
+ }
+
+ @Test
+ public void getResources_all() {
+ setupData("fixture");
+
+ List<ResourceDto> resources = dao.getResources(ResourceQuery.create());
+
+ assertThat(resources).hasSize(4);
+ }
+
+ @Test
+ public void getResources_filter_by_qualifier() {
+ setupData("fixture");
+
+ List<ResourceDto> resources = dao.getResources(ResourceQuery.create().setQualifiers(new String[] {"TRK", "BRC"}));
+ assertThat(resources).extracting("qualifier").containsOnly("TRK", "BRC");
+
+ resources = dao.getResources(ResourceQuery.create().setQualifiers(new String[] {"XXX"}));
+ assertThat(resources).isEmpty();
+
+ resources = dao.getResources(ResourceQuery.create().setQualifiers(new String[] {}));
+ assertThat(resources).hasSize(4);
+ }
+
+ @Test
+ public void getResources_filter_by_key() {
+ setupData("fixture");
+
+ ResourceQuery query = ResourceQuery.create().setKey("org.struts:struts-core");
+ List<ResourceDto> resources = dao.getResources(query);
+ assertThat(resources).hasSize(1);
+ assertThat(resources.get(0).getKey()).isEqualTo("org.struts:struts-core");
+
+ assertThat(dao.getResource(query).getKey()).isEqualTo("org.struts:struts-core");
+ }
+
+ @Test
+ public void getResourceIds_all() {
+ setupData("fixture");
+
+ List<Long> ids = dao.getResourceIds(ResourceQuery.create());
+
+ assertThat(ids).hasSize(4);
+ }
+
+ @Test
+ public void getResourceIds_filter_by_qualifier() {
+ setupData("fixture");
+
+ List<Long> ids = dao.getResourceIds(ResourceQuery.create().setQualifiers(new String[] {"TRK", "BRC"}));
+ assertThat(ids).containsOnly(1L, 2L);
+
+ ids = dao.getResourceIds(ResourceQuery.create().setQualifiers(new String[] {"XXX"}));
+ assertThat(ids).isEmpty();
+
+ ids = dao.getResourceIds(ResourceQuery.create().setQualifiers(new String[] {}));
+ assertThat(ids).hasSize(4);
+ }
+
+ @Test
+ public void getResources_exclude_disabled() {
+ setupData("getResources_exclude_disabled");
+
+ assertThat(dao.getResourceIds(ResourceQuery.create().setExcludeDisabled(false))).containsOnly(1L, 2L);
+ assertThat(dao.getResourceIds(ResourceQuery.create().setExcludeDisabled(true))).containsOnly(2L);
+ }
+
+ @Test
+ public void find_root_project_by_component_key() {
+ setupData("fixture");
+
+ assertThat(dao.getRootProjectByComponentKey("org.struts:struts-core:src/org/struts/RequestContext.java").getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getRootProjectByComponentKey("org.struts:struts-core:src/org/struts").getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getRootProjectByComponentKey("org.struts:struts-core").getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getRootProjectByComponentKey("org.struts:struts").getKey()).isEqualTo("org.struts:struts");
+
+ assertThat(dao.getRootProjectByComponentKey("unknown")).isNull();
+ }
+
+ @Test
+ public void find_root_project_by_component_Id() {
+ setupData("fixture");
+
+ assertThat(dao.getRootProjectByComponentId(4l).getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getRootProjectByComponentId(3l).getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getRootProjectByComponentId(2l).getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getRootProjectByComponentId(1l).getKey()).isEqualTo("org.struts:struts");
+ }
+
+ @Test
+ public void find_parent_by_component_id() {
+ setupData("fixture");
+
+ assertThat(dao.getParentModuleByComponentId(4l, session).getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getParentModuleByComponentId(3l, session).getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getParentModuleByComponentId(2l, session).getKey()).isEqualTo("org.struts:struts");
+ assertThat(dao.getParentModuleByComponentId(1l, session).getKey()).isEqualTo("org.struts:struts");
+ }
+
+ @Test
+ public void should_update() {
+ setupData("update");
+
+ ResourceDto project = new ResourceDto()
+ .setKey("org.struts:struts")
+ .setDeprecatedKey("deprecated key").setScope(Scopes.PROJECT).setQualifier(Qualifiers.PROJECT)
+ .setName("Struts").setLongName("Apache Struts").setLanguage("java").setDescription("MVC Framework")
+ .setPath("/foo/bar")
+ .setId(1L);
+
+ dao.insertOrUpdate(project);
+
+ assertThat(project.getId()).isNotNull();
+ checkTables("update", "projects");
+ }
+
+ @Test
+ public void should_insert() {
+ setupData("insert");
+
+ ResourceDto file1 = new ResourceDto()
+ .setUuid("ABCD").setProjectUuid("EFGH").setModuleUuid("EFGH").setModuleUuidPath(".EFGH.")
+ .setKey("org.struts:struts:/src/main/java/org/struts/Action.java")
+ .setDeprecatedKey("org.struts:struts:org.struts.Action").setScope(Scopes.FILE).setQualifier(Qualifiers.FILE)
+ .setLanguage("java").setName("Action").setLongName("org.struts.Action").setPath("/foo/bar");
+ ResourceDto file2 = new ResourceDto()
+ .setUuid("BCDE").setProjectUuid("FGHI").setModuleUuid("FGHI").setModuleUuidPath(".FGHI.")
+ .setKey("org.struts:struts:/src/main/java/org/struts/Filter.java")
+ .setDeprecatedKey("org.struts:struts:org.struts.Filter").setScope(Scopes.FILE).setQualifier(Qualifiers.FILE)
+ .setLanguage("java").setName("Filter").setLongName("org.struts.Filter");
+
+ dao.insertOrUpdate(file1, file2);
+
+ assertThat(file1.getId()).isNotNull();
+ assertThat(file2.getId()).isNotNull();
+ checkTables("insert", new String[] {"authorization_updated_at", "created_at"}, "projects");
+
+ // SONAR-3636 : created_at must be fed when inserting a new entry in the 'projects' table
+ ResourceDto fileLoadedFromDB = dao.getResource(file1.getId());
+ assertThat(fileLoadedFromDB.getCreatedAt()).isNotNull();
+ assertThat(fileLoadedFromDB.getAuthorizationUpdatedAt()).isNotNull();
+ }
+
+ @Test
+ public void should_insert_using_existing_session() {
+ setupData("insert");
+
+ ResourceDto file1 = new ResourceDto().setUuid("ABCD")
+ .setKey("org.struts:struts:/src/main/java/org/struts/Action.java")
+ .setDeprecatedKey("org.struts:struts:org.struts.Action").setScope(Scopes.FILE).setQualifier(Qualifiers.FILE)
+ .setLanguage("java").setName("Action").setLongName("org.struts.Action");
+ ResourceDto file2 = new ResourceDto().setUuid("BCDE")
+ .setKey("org.struts:struts:/src/main/java/org/struts/Filter.java")
+ .setDeprecatedKey("org.struts:struts:org.struts.Filter").setScope(Scopes.FILE).setQualifier(Qualifiers.FILE)
+ .setLanguage("java").setName("Filter").setLongName("org.struts.Filter");
+
+ SqlSession session = getMyBatis().openSession();
+
+ dao.insertUsingExistingSession(file1, session);
+ dao.insertUsingExistingSession(file2, session);
+
+ session.rollback();
+
+ assertEmptyTables("projects");
+ }
+
+ @Test
+ public void insert_add_uuids_on_project_if_missing() {
+ setupData("insert");
+
+ ResourceDto project = new ResourceDto()
+ .setKey("org.struts:struts:struts")
+ .setScope(Scopes.PROJECT)
+ .setQualifier(Qualifiers.PROJECT);
+
+ ResourceDto file = new ResourceDto()
+ .setKey("org.struts:struts:/src/main/java/org/struts/Action.java")
+ .setScope(Scopes.FILE)
+ .setQualifier(Qualifiers.FILE);
+
+ dao.insertOrUpdate(project, file);
+
+ assertThat(project.getUuid()).isNotNull();
+ assertThat(project.getProjectUuid()).isEqualTo(project.getUuid());
+ assertThat(project.getModuleUuidPath()).isEmpty();
+
+ assertThat(file.getUuid()).isNull();
+ assertThat(file.getProjectUuid()).isNull();
+ assertThat(file.getModuleUuidPath()).isNull();
+ }
+
+ @Test
+ public void should_find_component_by_key() {
+ setupData("fixture");
+
+ assertThat(dao.findByKey("org.struts:struts")).isNotNull();
+ Component component = dao.findByKey("org.struts:struts-core:src/org/struts/RequestContext.java");
+ assertThat(component).isNotNull();
+ assertThat(component.path()).isEqualTo("src/org/struts/RequestContext.java");
+ assertThat(dao.findByKey("unknown")).isNull();
+ }
+
+ @Test
+ public void should_find_component_by_id() {
+ setupData("fixture");
+
+ assertThat(dao.findById(1L, session)).isNotNull();
+ assertThat(dao.findById(4L, session)).isNotNull();
+ assertThat(dao.findById(555L, session)).isNull();
+ }
+
+ @Test
+ public void should_select_projects_by_qualifiers() {
+ setupData("fixture-including-ghost-projects-and-technical-project");
+
+ List<Component> components = dao.selectProjectsByQualifiers(newArrayList("TRK"));
+ assertThat(components).hasSize(1);
+ assertThat(components.get(0).key()).isEqualTo("org.struts:struts");
+ assertThat(((ComponentDto) components.get(0)).getId()).isEqualTo(1L);
+
+ assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(newArrayList("unknown"))).isEmpty();
+ assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(Collections.<String>emptyList())).isEmpty();
+ }
+
+ @Test
+ public void should_select_projects_including_not_finished_by_qualifiers() {
+ setupData("fixture-including-ghost-projects-and-technical-project");
+
+ List<Component> components = dao.selectProjectsIncludingNotCompletedOnesByQualifiers(newArrayList("TRK"));
+ assertThat(getKeys(components)).containsOnly("org.struts:struts", "org.apache.shindig", "org.sample:sample");
+
+ assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(newArrayList("unknown"))).isEmpty();
+ assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(Collections.<String>emptyList())).isEmpty();
+ }
+
+ @Test
+ public void should_select_ghosts_projects_by_qualifiers() {
+ setupData("fixture-including-ghost-projects-and-technical-project");
+
+ List<Component> components = dao.selectGhostsProjects(newArrayList("TRK"));
+ assertThat(components).hasSize(1);
+ assertThat(getKeys(components)).containsOnly("org.apache.shindig");
+
+ assertThat(dao.selectGhostsProjects(newArrayList("unknown"))).isEmpty();
+ assertThat(dao.selectGhostsProjects(Collections.<String>emptyList())).isEmpty();
+ }
+
+ @Test
+ public void should_select_provisioned_projects_by_qualifiers() {
+ setupData("fixture-including-ghost-projects-and-technical-project");
+
+ List<ResourceDto> components = dao.selectProvisionedProjects(newArrayList("TRK"));
+ assertThat(components).hasSize(1);
+ assertThat(components.get(0).getKey()).isEqualTo("org.sample:sample");
+
+ assertThat(dao.selectProvisionedProjects(newArrayList("unknown"))).isEmpty();
+ assertThat(dao.selectProvisionedProjects(Collections.<String>emptyList())).isEmpty();
+ }
+
+ @Test
+ public void should_select_provisioned_project_by_key() {
+ setupData("fixture-including-ghost-projects-and-technical-project");
+
+ String key = "org.sample:sample";
+ assertThat(dao.selectProvisionedProject(key).getKey()).isEqualTo(key);
+ assertThat(dao.selectProvisionedProject("unknown")).isNull();
+ }
+
+ @Test
+ public void get_last_snapshot_by_component_uuid() {
+ setupData("get_last_snapshot_by_component_uuid");
+
+ SnapshotDto snapshotDto = dao.getLastSnapshotByResourceUuid("ABCD", session);
+ assertThat(snapshotDto.getId()).isEqualTo(1);
+
+ assertThat(snapshotDto.getPeriodMode(1)).isEqualTo("previous_analysis");
+ assertThat(snapshotDto.getPeriodModeParameter(1)).isNull();
+ assertThat(snapshotDto.getPeriodDate(1)).isNull();
+
+ assertThat(snapshotDto.getPeriodMode(2)).isEqualTo("days");
+ assertThat(snapshotDto.getPeriodModeParameter(2)).isEqualTo("30");
+ assertThat(snapshotDto.getPeriodDate(2)).isEqualTo(1_316_815_200_000L);
+
+ assertThat(snapshotDto.getPeriodMode(3)).isEqualTo("days");
+ assertThat(snapshotDto.getPeriodModeParameter(3)).isEqualTo("90");
+ assertThat(snapshotDto.getPeriodDate(3)).isEqualTo(1_311_631_200_000L);
+
+ assertThat(snapshotDto.getPeriodMode(4)).isEqualTo("previous_analysis");
+ assertThat(snapshotDto.getPeriodModeParameter(4)).isNull();
+ assertThat(snapshotDto.getPeriodDate(4)).isNull();
+
+ assertThat(snapshotDto.getPeriodMode(5)).isEqualTo("previous_version");
+ assertThat(snapshotDto.getPeriodModeParameter(5)).isNull();
+ assertThat(snapshotDto.getPeriodDate(5)).isNull();
+
+ snapshotDto = dao.getLastSnapshotByResourceUuid("EFGH", session);
+ assertThat(snapshotDto.getId()).isEqualTo(2L);
+
+ snapshotDto = dao.getLastSnapshotByResourceUuid("GHIJ", session);
+ assertThat(snapshotDto.getId()).isEqualTo(3L);
+
+ assertThat(dao.getLastSnapshotByResourceUuid("UNKNOWN", session)).isNull();
+ }
+
+ @Test
+ public void update_authorization_date() {
+ setupData("update_authorization_date");
+
+ when(system2.now()).thenReturn(987654321L);
+ dao.updateAuthorizationDate(1L, session);
+ session.commit();
+
+ checkTables("update_authorization_date", "projects");
+ }
+
+ private List<String> getKeys(final List<Component> components) {
+ return newArrayList(Iterables.transform(components, new Function<Component, String>() {
+ @Override
+ public String apply(@Nullable Component input) {
+ return input.key();
+ }
+ }));
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ResourceIndexerDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ResourceIndexerDaoTest.java
new file mode 100644
index 00000000000..90329d5842b
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/ResourceIndexerDaoTest.java
@@ -0,0 +1,153 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import org.hamcrest.core.Is;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+public class ResourceIndexerDaoTest extends AbstractDaoTestCase {
+
+ private static ResourceIndexerDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new ResourceIndexerDao(getMyBatis());
+ }
+
+ @Test
+ public void shouldIndexResource() {
+ setupData("shouldIndexResource");
+
+ dao.indexResource(10, "ZipUtils", "FIL", 8);
+
+ checkTables("shouldIndexResource", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldIndexProjects() {
+ setupData("shouldIndexProjects");
+
+ dao.indexProjects();
+
+ checkTables("shouldIndexProjects", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldIndexMultiModulesProject() {
+ setupData("shouldIndexMultiModulesProject");
+
+ dao.indexProject(1);
+
+ checkTables("shouldIndexMultiModulesProject", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldReindexProjectAfterRenaming() {
+ setupData("shouldReindexProjectAfterRenaming");
+
+ dao.indexProject(1);
+
+ checkTables("shouldReindexProjectAfterRenaming", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldNotIndexPackages() throws SQLException {
+ setupData("shouldNotIndexPackages");
+
+ dao.indexProject(1);
+
+ Connection connection = getConnection();
+ ResultSet rs = null;
+ try {
+ // project
+ rs = connection.createStatement().executeQuery("select count(resource_id) from resource_index where resource_id=1");
+ rs.next();
+ assertThat(rs.getInt(1), greaterThan(0));
+
+ // directory
+ rs = connection.createStatement().executeQuery("select count(resource_id) from resource_index where resource_id=2");
+ rs.next();
+ assertThat(rs.getInt(1), Is.is(0));
+
+ // file
+ rs = connection.createStatement().executeQuery("select count(resource_id) from resource_index where resource_id=3");
+ rs.next();
+ assertThat(rs.getInt(1), greaterThan(0));
+ } finally {
+ if (null != rs) {
+ rs.close();
+ }
+ }
+ }
+
+ @Test
+ public void shouldIndexTwoLettersLongResources() {
+ setupData("shouldIndexTwoLettersLongResource");
+
+ dao.indexResource(10, "AB", Qualifiers.PROJECT, 3);
+
+ checkTables("shouldIndexTwoLettersLongResource", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldReIndexTwoLettersLongResources() {
+ setupData("shouldReIndexTwoLettersLongResource");
+
+ dao.indexResource(1, "AS", Qualifiers.PROJECT, 1);
+
+ checkTables("shouldReIndexTwoLettersLongResource", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldReIndexNewTwoLettersLongResource() {
+ setupData("shouldReIndexNewTwoLettersLongResource");
+
+ dao.indexResource(1, "AS", Qualifiers.PROJECT, 1);
+
+ checkTables("shouldReIndexNewTwoLettersLongResource", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldReindexResource() {
+ setupData("shouldReindexResource");
+
+ dao.indexResource(1, "New Struts", Qualifiers.PROJECT, 1);
+
+ checkTables("shouldReindexResource", new String[] {"id"}, "resource_index");
+ }
+
+ @Test
+ public void shouldNotReindexUnchangedResource() {
+ setupData("shouldNotReindexUnchangedResource");
+
+ dao.indexResource(1, "Struts", Qualifiers.PROJECT, 1);
+
+ checkTables("shouldNotReindexUnchangedResource", new String[] {"id"}, "resource_index");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/ResourceKeyUpdaterDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/ResourceKeyUpdaterDaoTest.java
new file mode 100644
index 00000000000..13e46936fb8
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/ResourceKeyUpdaterDaoTest.java
@@ -0,0 +1,110 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.component;
+
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ResourceKeyUpdaterDaoTest extends AbstractDaoTestCase {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private ResourceKeyUpdaterDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new ResourceKeyUpdaterDao(getMyBatis());
+ }
+
+ @Test
+ public void shouldUpdateKey() {
+ setupData("shared");
+
+ dao.updateKey(2, "struts:core");
+
+ checkTables("shouldUpdateKey", "projects");
+ }
+
+ @Test
+ public void shouldNotUpdateKey() {
+ setupData("shared");
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Impossible to update key: a resource with \"org.struts:struts-ui\" key already exists.");
+
+ dao.updateKey(2, "org.struts:struts-ui");
+ }
+
+ @Test
+ public void shouldBulkUpdateKey() {
+ setupData("shared");
+
+ dao.bulkUpdateKey(1, "org.struts", "org.apache.struts");
+
+ checkTables("shouldBulkUpdateKey", "projects");
+ }
+
+ @Test
+ public void shouldBulkUpdateKeyOnOnlyOneSubmodule() {
+ setupData("shared");
+
+ dao.bulkUpdateKey(1, "struts-ui", "struts-web");
+
+ checkTables("shouldBulkUpdateKeyOnOnlyOneSubmodule", "projects");
+ }
+
+ @Test
+ public void shouldFailBulkUpdateKeyIfKeyAlreadyExist() {
+ setupData("shared");
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("Impossible to update key: a resource with \"foo:struts-core\" key already exists.");
+
+ dao.bulkUpdateKey(1, "org.struts", "foo");
+ }
+
+ @Test
+ public void shouldNotUpdateAllSubmodules() {
+ setupData("shouldNotUpdateAllSubmodules");
+
+ dao.bulkUpdateKey(1, "org.struts", "org.apache.struts");
+
+ checkTables("shouldNotUpdateAllSubmodules", "projects");
+ }
+
+ @Test
+ public void shouldCheckModuleKeysBeforeRenaming() {
+ setupData("shared");
+
+ Map<String, String> checkResults = dao.checkModuleKeysBeforeRenaming(1, "org.struts", "foo");
+ assertThat(checkResults.size()).isEqualTo(3);
+ assertThat(checkResults.get("org.struts:struts")).isEqualTo("foo:struts");
+ assertThat(checkResults.get("org.struts:struts-core")).isEqualTo("#duplicate_key#");
+ assertThat(checkResults.get("org.struts:struts-ui")).isEqualTo("foo:struts-ui");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/SnapshotDtoTest.java b/sonar-db/src/test/java/org/sonar/db/component/SnapshotDtoTest.java
new file mode 100644
index 00000000000..360fd739238
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/SnapshotDtoTest.java
@@ -0,0 +1,107 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.utils.DateUtils.parseDate;
+
+public class SnapshotDtoTest {
+
+ @Test
+ public void test_getter_and_setter() throws Exception {
+ SnapshotDto snapshotDto = new SnapshotDto()
+ .setId(10L)
+ .setParentId(2L)
+ .setRootId(3L)
+ .setRootProjectId(20L)
+ .setBuildDate(parseDate("2014-07-02").getTime())
+ .setComponentId(21L)
+ .setLast(true)
+ .setScope("FIL")
+ .setQualifier("FIL")
+ .setVersion("1.0")
+ .setPath("3.2.")
+ .setDepth(1)
+ .setPeriodMode(1, "mode1")
+ .setPeriodMode(2, "mode2")
+ .setPeriodMode(3, "mode3")
+ .setPeriodMode(4, "mode4")
+ .setPeriodMode(5, "mode5")
+ .setPeriodParam(1, "param1")
+ .setPeriodParam(2, "param2")
+ .setPeriodParam(3, "param3")
+ .setPeriodParam(4, "param4")
+ .setPeriodParam(5, "param5")
+ .setPeriodDate(1, parseDate("2014-06-01").getTime())
+ .setPeriodDate(2, parseDate("2014-06-02").getTime())
+ .setPeriodDate(3, parseDate("2014-06-03").getTime())
+ .setPeriodDate(4, parseDate("2014-06-04").getTime())
+ .setPeriodDate(5, parseDate("2014-06-05").getTime());
+
+ assertThat(snapshotDto.getId()).isEqualTo(10L);
+ assertThat(snapshotDto.getParentId()).isEqualTo(2L);
+ assertThat(snapshotDto.getRootId()).isEqualTo(3L);
+ assertThat(snapshotDto.getRootProjectId()).isEqualTo(20L);
+ assertThat(snapshotDto.getBuildDate()).isEqualTo(parseDate("2014-07-02").getTime());
+ assertThat(snapshotDto.getComponentId()).isEqualTo(21L);
+ assertThat(snapshotDto.getLast()).isTrue();
+ assertThat(snapshotDto.getScope()).isEqualTo("FIL");
+ assertThat(snapshotDto.getQualifier()).isEqualTo("FIL");
+ assertThat(snapshotDto.getVersion()).isEqualTo("1.0");
+ assertThat(snapshotDto.getPath()).isEqualTo("3.2.");
+ assertThat(snapshotDto.getDepth()).isEqualTo(1);
+ assertThat(snapshotDto.getPeriodMode(1)).isEqualTo("mode1");
+ assertThat(snapshotDto.getPeriodMode(2)).isEqualTo("mode2");
+ assertThat(snapshotDto.getPeriodMode(3)).isEqualTo("mode3");
+ assertThat(snapshotDto.getPeriodMode(4)).isEqualTo("mode4");
+ assertThat(snapshotDto.getPeriodMode(5)).isEqualTo("mode5");
+ assertThat(snapshotDto.getPeriodModeParameter(1)).isEqualTo("param1");
+ assertThat(snapshotDto.getPeriodModeParameter(2)).isEqualTo("param2");
+ assertThat(snapshotDto.getPeriodModeParameter(3)).isEqualTo("param3");
+ assertThat(snapshotDto.getPeriodModeParameter(4)).isEqualTo("param4");
+ assertThat(snapshotDto.getPeriodModeParameter(5)).isEqualTo("param5");
+ assertThat(snapshotDto.getPeriodDate(1)).isEqualTo(parseDate("2014-06-01").getTime());
+ assertThat(snapshotDto.getPeriodDate(2)).isEqualTo(parseDate("2014-06-02").getTime());
+ assertThat(snapshotDto.getPeriodDate(3)).isEqualTo(parseDate("2014-06-03").getTime());
+ assertThat(snapshotDto.getPeriodDate(4)).isEqualTo(parseDate("2014-06-04").getTime());
+ assertThat(snapshotDto.getPeriodDate(5)).isEqualTo(parseDate("2014-06-05").getTime());
+ }
+
+ @Test
+ public void get_root_id_if_when_it_is_not_null() {
+ SnapshotDto snapshot = new SnapshotDto().setRootId(123L).setId(456L);
+
+ Long rootIdOrSelf = snapshot.getRootIdOrSelf();
+
+ assertThat(rootIdOrSelf).isEqualTo(123L);
+ }
+
+ @Test
+ public void getRootIdOrSelf_return_own_id_when_root_id_is_null() {
+ SnapshotDto snapshot = new SnapshotDto().setRootId(null).setId(456L);
+
+ Long rootIdOrSelf = snapshot.getRootIdOrSelf();
+
+ assertThat(rootIdOrSelf).isEqualTo(456L);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/component/SnapshotQueryTest.java b/sonar-db/src/test/java/org/sonar/db/component/SnapshotQueryTest.java
new file mode 100644
index 00000000000..613686ec55b
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/component/SnapshotQueryTest.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.component;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
+import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC;
+
+public class SnapshotQueryTest {
+
+ @Test
+ public void test_setters_and_getters() throws Exception {
+ SnapshotQuery query = new SnapshotQuery()
+ .setComponentId(1L)
+ .setIsLast(true)
+ .setStatus("P")
+ .setVersion("1.0")
+ .setCreatedAfter(10L)
+ .setCreatedBefore(20L)
+ .setSort(BY_DATE, ASC);
+
+ assertThat(query.getComponentId()).isEqualTo(1L);
+ assertThat(query.getIsLast()).isTrue();
+ assertThat(query.getStatus()).isEqualTo("P");
+ assertThat(query.getVersion()).isEqualTo("1.0");
+ assertThat(query.getCreatedAfter()).isEqualTo(10L);
+ assertThat(query.getCreatedBefore()).isEqualTo(20L);
+ assertThat(query.getSortField()).isEqualTo("created_at");
+ assertThat(query.getSortOrder()).isEqualTo("asc");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dashboard/ActiveDashboardDaoTest.java b/sonar-db/src/test/java/org/sonar/db/dashboard/ActiveDashboardDaoTest.java
new file mode 100644
index 00000000000..8f320764923
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dashboard/ActiveDashboardDaoTest.java
@@ -0,0 +1,115 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.db.DbTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class ActiveDashboardDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ private ActiveDashboardDao dao;
+
+ @Before
+ public void createDao() {
+ dbTester.truncateTables();
+ dao = new ActiveDashboardDao(dbTester.myBatis());
+ }
+
+ @Test
+ public void shouldInsert() {
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
+
+ ActiveDashboardDto dashboard = new ActiveDashboardDto();
+ dashboard.setDashboardId(2L);
+ dashboard.setUserId(3L);
+ dashboard.setOrderIndex(4);
+ dao.insert(dashboard);
+
+ dbTester.assertDbUnit(getClass(), "shouldInsert-result.xml", "active_dashboards");
+ }
+
+ @Test
+ public void shouldInsertWithNoUser() {
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
+
+ ActiveDashboardDto dashboard = new ActiveDashboardDto();
+ dashboard.setDashboardId(2L);
+ dashboard.setOrderIndex(4);
+ dao.insert(dashboard);
+
+ dbTester.assertDbUnit(getClass(), "shouldInsertWithNoUser-result.xml", "active_dashboards");
+ }
+
+ @Test
+ public void shouldGetMaxOrderIndexForNullUser() {
+ dbTester.prepareDbUnit(getClass(), "shouldGetMaxOrderIndexForNullUser.xml");
+
+ int index = dao.selectMaxOrderIndexForNullUser();
+
+ assertThat(index).isEqualTo(15);
+ }
+
+ @Test
+ public void shouldGetZeroMaxOrderIndex() {
+ dbTester.prepareDbUnit(getClass(), "empty.xml");
+
+ int index = dao.selectMaxOrderIndexForNullUser();
+
+ assertThat(index).isZero();
+ }
+
+ @Test
+ public void should_get_dashboards_for_anonymous() {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectDashboardsForAnonymous.xml");
+
+ assertThat(dao.selectGlobalDashboardsForUserLogin(null)).hasSize(2).extracting("id").containsExactly(2L, 1L);
+ }
+
+ @Test
+ public void should_get_dashboards_for_user() {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectDashboardsForUser.xml");
+
+ assertThat(dao.selectGlobalDashboardsForUserLogin("obiwan")).hasSize(2).extracting("id").containsExactly(2L, 1L);
+ }
+
+ @Test
+ public void should_get_project_dashboards_for_anonymous() {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectProjectDashboardsForAnonymous.xml");
+
+ assertThat(dao.selectProjectDashboardsForUserLogin(null)).hasSize(2).extracting("id").containsExactly(2L, 1L);
+ }
+
+ @Test
+ public void should_get_project_dashboards_for_user() {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectProjectDashboardsForUser.xml");
+
+ assertThat(dao.selectProjectDashboardsForUserLogin("obiwan")).hasSize(2).extracting("id").containsExactly(2L, 1L);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dashboard/DashboardDaoTest.java b/sonar-db/src/test/java/org/sonar/db/dashboard/DashboardDaoTest.java
new file mode 100644
index 00000000000..bcb505fc176
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dashboard/DashboardDaoTest.java
@@ -0,0 +1,130 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dashboard;
+
+import java.util.Date;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.db.DbTester;
+import org.sonar.test.DbTests;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+@Category(DbTests.class)
+public class DashboardDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ private DashboardDao dao;
+
+ @Before
+ public void createDao() {
+ dbTester.truncateTables();
+ dao = new DashboardDao(dbTester.myBatis());
+ }
+
+ @Test
+ public void shouldSelectGlobalDashboard() {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectGlobalDashboard.xml");
+ DashboardDto dashboard = dao.selectGlobalDashboard("SQALE");
+ assertThat(dashboard.getId(), is(2L));
+ assertThat(dashboard.getUserId(), nullValue());
+
+ assertNull(dao.selectGlobalDashboard("unknown"));
+ }
+
+ @Test
+ public void shouldInsert() {
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
+ Date aDate = new Date();
+
+ DashboardDto dashboardDto = new DashboardDto();
+ dashboardDto.setUserId(6L);
+ dashboardDto.setName("My Dashboard");
+ dashboardDto.setDescription("This is a dashboard");
+ dashboardDto.setColumnLayout("100%");
+ dashboardDto.setShared(true);
+ dashboardDto.setGlobal(true);
+ dashboardDto.setCreatedAt(aDate);
+ dashboardDto.setUpdatedAt(aDate);
+
+ WidgetDto widgetDto = new WidgetDto();
+ widgetDto.setWidgetKey("code_coverage");
+ widgetDto.setName("Code coverage");
+ widgetDto.setDescription("Widget for code coverage");
+ widgetDto.setColumnIndex(13);
+ widgetDto.setRowIndex(14);
+ widgetDto.setConfigured(true);
+ widgetDto.setCreatedAt(aDate);
+ widgetDto.setUpdatedAt(aDate);
+ dashboardDto.addWidget(widgetDto);
+
+ WidgetPropertyDto property = new WidgetPropertyDto();
+ property.setPropertyKey("displayITs");
+ property.setTextValue("true");
+ widgetDto.addWidgetProperty(property);
+
+ dao.insert(dashboardDto);
+
+ dbTester.assertDbUnit(getClass(), "shouldInsert-result.xml", new String[] {"created_at", "updated_at"}, "dashboards", "widgets", "widget_properties");
+ }
+
+ @Test
+ public void shouldInsertWithNullableColumns() {
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
+
+ DashboardDto dashboardDto = new DashboardDto();
+ dashboardDto.setUserId(null);
+ dashboardDto.setName(null);
+ dashboardDto.setDescription(null);
+ dashboardDto.setColumnLayout(null);
+ dashboardDto.setShared(true);
+ dashboardDto.setGlobal(false);
+ dashboardDto.setCreatedAt(null);
+ dashboardDto.setUpdatedAt(null);
+
+ WidgetDto widgetDto = new WidgetDto();
+ widgetDto.setWidgetKey("code_coverage");
+ widgetDto.setName(null);
+ widgetDto.setDescription(null);
+ widgetDto.setColumnIndex(null);
+ widgetDto.setRowIndex(null);
+ widgetDto.setConfigured(true);
+ widgetDto.setCreatedAt(null);
+ widgetDto.setUpdatedAt(null);
+ dashboardDto.addWidget(widgetDto);
+
+ WidgetPropertyDto property = new WidgetPropertyDto();
+ property.setPropertyKey(null);
+ property.setTextValue(null);
+ widgetDto.addWidgetProperty(property);
+
+ dao.insert(dashboardDto);
+
+ dbTester.assertDbUnit(getClass(), "shouldInsertWithNullableColumns-result.xml", "dashboards", "widgets", "widget_properties");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDaoTest.java b/sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDaoTest.java
new file mode 100644
index 00000000000..504fe7d9246
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDaoTest.java
@@ -0,0 +1,244 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.debt;
+
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.test.DbTests;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class CharacteristicDaoTest {
+
+ private static final String[] EXCLUDED_COLUMNS = new String[] {"id", "root_id", "rule_id", "function_key", "factor_unit", "factor_value", "offset_unit", "offset_value"};
+
+ @ClassRule
+ public static DbTester db = new DbTester();
+
+ CharacteristicDao dao;
+
+ DbSession session;
+
+ @Before
+ public void createDao() {
+ db.truncateTables();
+ session = db.myBatis().openSession(false);
+ dao = new CharacteristicDao(db.myBatis());
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void select_enabled_characteristics() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ List<CharacteristicDto> dtos = dao.selectEnabledCharacteristics();
+
+ assertThat(dtos).hasSize(2);
+
+ CharacteristicDto rootCharacteristic = dtos.get(0);
+ assertThat(rootCharacteristic.getId()).isEqualTo(1);
+ assertThat(rootCharacteristic.getKey()).isEqualTo("PORTABILITY");
+ assertThat(rootCharacteristic.getName()).isEqualTo("Portability");
+ assertThat(rootCharacteristic.getParentId()).isNull();
+ assertThat(rootCharacteristic.getOrder()).isEqualTo(1);
+ assertThat(rootCharacteristic.isEnabled()).isTrue();
+ assertThat(rootCharacteristic.getCreatedAt()).isNotNull();
+ assertThat(rootCharacteristic.getUpdatedAt()).isNotNull();
+
+ CharacteristicDto characteristic = dtos.get(1);
+ assertThat(characteristic.getId()).isEqualTo(2);
+ assertThat(characteristic.getKey()).isEqualTo("COMPILER_RELATED_PORTABILITY");
+ assertThat(characteristic.getName()).isEqualTo("Compiler related portability");
+ assertThat(characteristic.getParentId()).isEqualTo(1);
+ assertThat(characteristic.getOrder()).isNull();
+ assertThat(characteristic.isEnabled()).isTrue();
+ assertThat(characteristic.getCreatedAt()).isNotNull();
+ assertThat(characteristic.getUpdatedAt()).isNotNull();
+ }
+
+ @Test
+ public void select_characteristics() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ assertThat(dao.selectCharacteristics()).hasSize(4);
+ }
+
+ @Test
+ public void select_enabled_root_characteristics() {
+ db.prepareDbUnit(getClass(), "select_enabled_root_characteristics.xml");
+
+ List<CharacteristicDto> dtos = dao.selectEnabledRootCharacteristics();
+
+ assertThat(dtos).hasSize(1);
+
+ CharacteristicDto rootCharacteristic = dtos.get(0);
+ assertThat(rootCharacteristic.getId()).isEqualTo(1);
+ assertThat(rootCharacteristic.getKey()).isEqualTo("PORTABILITY");
+ }
+
+ @Test
+ public void select_enabled_root_characteristics_order_by_characteristic_order() {
+ db.prepareDbUnit(getClass(), "select_enabled_root_characteristics_order_by_characteristic_order.xml");
+
+ List<CharacteristicDto> dtos = dao.selectEnabledRootCharacteristics();
+
+ assertThat(dtos).hasSize(3);
+ assertThat(dtos.get(0).getKey()).isEqualTo("TESTABILITY");
+ assertThat(dtos.get(1).getKey()).isEqualTo("PORTABILITY");
+ assertThat(dtos.get(2).getKey()).isEqualTo("MAINTAINABILITY");
+ }
+
+ @Test
+ public void select_sub_characteristics_by_parent_id() {
+ db.prepareDbUnit(getClass(), "select_sub_characteristics_by_parent_id.xml");
+
+ assertThat(dao.selectCharacteristicsByParentId(1)).hasSize(2);
+ assertThat(dao.selectCharacteristicsByParentId(55)).isEmpty();
+ }
+
+ @Test
+ public void select_characteristics_by_ids() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ assertThat(dao.selectCharacteristicsByIds(newArrayList(1, 2))).hasSize(2);
+ assertThat(dao.selectCharacteristicsByIds(newArrayList(1))).hasSize(1);
+
+ // Disabled characteristics are not returned
+ assertThat(dao.selectCharacteristicsByIds(newArrayList(4, 5))).isEmpty();
+ }
+
+ @Test
+ public void select_characteristic_by_key() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ CharacteristicDto dto = dao.selectByKey("COMPILER_RELATED_PORTABILITY");
+ assertThat(dto).isNotNull();
+ assertThat(dto.getId()).isEqualTo(2);
+ assertThat(dto.getParentId()).isEqualTo(1);
+
+ dto = dao.selectByKey("PORTABILITY");
+ assertThat(dto).isNotNull();
+ assertThat(dto.getId()).isEqualTo(1);
+ assertThat(dto.getParentId()).isNull();
+
+ assertThat(dao.selectByKey("UNKNOWN")).isNull();
+ }
+
+ @Test
+ public void select_characteristic_by_name() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ assertThat(dao.selectByName("Portability")).isNotNull();
+ assertThat(dao.selectByName("Compiler related portability")).isNotNull();
+ assertThat(dao.selectByName("Unknown")).isNull();
+ }
+
+ @Test
+ public void select_characteristic_by_id() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ assertThat(dao.selectById(2)).isNotNull();
+ assertThat(dao.selectById(1)).isNotNull();
+
+ assertThat(dao.selectById(10)).isNull();
+ }
+
+ @Test
+ public void select_max_characteristic_order() {
+ db.prepareDbUnit(getClass(), "shared.xml");
+
+ assertThat(dao.selectMaxCharacteristicOrder()).isEqualTo(1);
+ }
+
+ @Test
+ public void select_max_characteristic_order_when_characteristics_are_all_disabled() {
+ db.prepareDbUnit(getClass(), "select_max_characteristic_order_when_characteristics_are_all_disabled.xml");
+
+ assertThat(dao.selectMaxCharacteristicOrder()).isEqualTo(0);
+ }
+
+ @Test
+ public void insert_characteristic() {
+ CharacteristicDto dto = new CharacteristicDto()
+ .setKey("COMPILER_RELATED_PORTABILITY")
+ .setName("Compiler related portability")
+ .setOrder(1)
+ .setEnabled(true)
+ .setCreatedAt(DateUtils.parseDate("2013-11-20"));
+
+ dao.insert(dto);
+
+ db.assertDbUnit(getClass(), "insert_characteristic-result.xml", EXCLUDED_COLUMNS, "characteristics");
+ }
+
+ @Test
+ public void insert_characteristics() {
+ dao.insert(session, new CharacteristicDto()
+ .setKey("COMPILER_RELATED_PORTABILITY")
+ .setName("Compiler related portability")
+ .setOrder(1)
+ .setEnabled(true)
+ .setCreatedAt(DateUtils.parseDate("2013-11-20")),
+ new CharacteristicDto()
+ .setKey("PORTABILITY")
+ .setName("portability")
+ .setOrder(2)
+ .setEnabled(true)
+ .setCreatedAt(DateUtils.parseDate("2013-11-20")));
+ session.commit();
+
+ assertThat(db.countRowsOfTable("characteristics")).isEqualTo(2);
+ }
+
+ @Test
+ public void update_characteristic() {
+ db.prepareDbUnit(getClass(), "update_characteristic.xml");
+
+ CharacteristicDto dto = new CharacteristicDto()
+ .setId(1)
+ // The Key should not be changed
+ .setKey("NEW_KEY")
+ .setName("New name")
+ .setOrder(2)
+ // Created date should not changed
+ .setCreatedAt(DateUtils.parseDate("2013-11-22"))
+ .setUpdatedAt(DateUtils.parseDate("2014-03-19"))
+ .setEnabled(false);
+
+ dao.update(dto);
+
+ db.assertDbUnit(getClass(), "update_characteristic-result.xml", EXCLUDED_COLUMNS, "characteristics");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDtoTest.java b/sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDtoTest.java
new file mode 100644
index 00000000000..8ecfbb32dae
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/debt/CharacteristicDtoTest.java
@@ -0,0 +1,84 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.debt;
+
+import java.util.Date;
+import org.junit.Test;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CharacteristicDtoTest {
+
+ @Test
+ public void to_dto_from_characteristic() {
+ DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic()
+ .setId(1)
+ .setKey("MEMORY_EFFICIENCY")
+ .setName("Memory use");
+
+ DefaultCharacteristic characteristic = new DefaultCharacteristic()
+ .setId(2)
+ .setKey("EFFICIENCY")
+ .setName("Efficiency")
+ .setParent(rootCharacteristic)
+ .setOrder(5)
+ .setCreatedAt(new Date())
+ .setUpdatedAt(new Date());
+
+ CharacteristicDto dto = CharacteristicDto.toDto(characteristic, 1);
+ assertThat(dto.getId()).isNull();
+ assertThat(dto.getParentId()).isEqualTo(1);
+ assertThat(dto.getKey()).isEqualTo("EFFICIENCY");
+ assertThat(dto.getName()).isEqualTo("Efficiency");
+ assertThat(dto.getOrder()).isEqualTo(5);
+ assertThat(dto.isEnabled()).isTrue();
+ assertThat(dto.getCreatedAt()).isNotNull();
+ assertThat(dto.getUpdatedAt()).isNotNull();
+ }
+
+ @Test
+ public void to_characteristic() {
+ DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic()
+ .setId(1)
+ .setKey("MEMORY_EFFICIENCY")
+ .setName("Memory use");
+
+ CharacteristicDto dto = new CharacteristicDto()
+ .setId(2)
+ .setParentId(1)
+ .setKey("EFFICIENCY")
+ .setName("Efficiency")
+ .setOrder(5)
+ .setEnabled(false)
+ .setCreatedAt(new Date())
+ .setUpdatedAt(new Date());
+
+ DefaultCharacteristic characteristic = dto.toCharacteristic(rootCharacteristic);
+ assertThat(characteristic.id()).isEqualTo(2);
+ assertThat(characteristic.parent()).isEqualTo(rootCharacteristic);
+ assertThat(characteristic.key()).isEqualTo("EFFICIENCY");
+ assertThat(characteristic.name()).isEqualTo("Efficiency");
+ assertThat(characteristic.order()).isEqualTo(5);
+ assertThat(characteristic.createdAt()).isNotNull();
+ assertThat(characteristic.updatedAt()).isNotNull();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java b/sonar-db/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java
new file mode 100644
index 00000000000..32621935884
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.junit.Test;
+import org.sonar.api.utils.MessageException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DialectUtilsTest {
+
+ @Test
+ public void testFindById() {
+ Dialect d = DialectUtils.find("mysql", null);
+ assertThat(d).isInstanceOf(MySql.class);
+ }
+
+ @Test
+ public void testFindByJdbcUrl() {
+ Dialect d = DialectUtils.find(null, "jdbc:mysql:foo:bar");
+ assertThat(d).isInstanceOf(MySql.class);
+ }
+
+ @Test(expected = MessageException.class)
+ public void testFindNoMatch() {
+ DialectUtils.find("foo", "bar");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/H2Test.java b/sonar-db/src/test/java/org/sonar/db/dialect/H2Test.java
new file mode 100644
index 00000000000..bd230b1b580
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dialect/H2Test.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class H2Test {
+
+ H2 dialect = new H2();
+
+ @Test
+ public void matchesJdbcURL() {
+ assertThat(dialect.matchesJdbcURL("jdbc:h2:foo")).isTrue();
+ assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse();
+ }
+
+ @Test
+ public void testBooleanSqlValues() {
+ assertThat(dialect.getTrueSqlValue()).isEqualTo("true");
+ assertThat(dialect.getFalseSqlValue()).isEqualTo("false");
+ }
+
+ @Test
+ public void should_configure() {
+ assertThat(dialect.getId()).isEqualTo("h2");
+ assertThat(dialect.getActiveRecordDialectCode()).isEqualTo(".h2.");
+ assertThat(dialect.getDefaultDriverClassName()).isEqualTo("org.h2.Driver");
+ assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1");
+ }
+
+ @Test
+ public void testFetchSizeForScrolling() throws Exception {
+ assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200);
+ }
+
+ @Test
+ public void h2_does_not_supportMigration() {
+ assertThat(dialect.supportsMigration()).isFalse();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/MsSqlTest.java b/sonar-db/src/test/java/org/sonar/db/dialect/MsSqlTest.java
new file mode 100644
index 00000000000..3b1ebb14989
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dialect/MsSqlTest.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MsSqlTest {
+
+ private MsSql msSql = new MsSql();
+
+ @Test
+ public void matchesJdbcURL() {
+ assertThat(msSql.matchesJdbcURL("jdbc:jtds:sqlserver://localhost;databaseName=SONAR;SelectMethod=Cursor")).isTrue();
+ assertThat(msSql.matchesJdbcURL("jdbc:microsoft:sqlserver://localhost:1433;databasename=sonar")).isTrue();
+
+ assertThat(msSql.matchesJdbcURL("jdbc:hsql:foo")).isFalse();
+ assertThat(msSql.matchesJdbcURL("jdbc:mysql:foo")).isFalse();
+ }
+
+ @Test
+ public void testBooleanSqlValues() {
+ assertThat(msSql.getTrueSqlValue()).isEqualTo("1");
+ assertThat(msSql.getFalseSqlValue()).isEqualTo("0");
+ }
+
+ @Test
+ public void should_configure() {
+ assertThat(msSql.getId()).isEqualTo("mssql");
+ assertThat(msSql.getActiveRecordDialectCode()).isEqualTo("sqlserver");
+ assertThat(msSql.getDefaultDriverClassName()).isEqualTo("net.sourceforge.jtds.jdbc.Driver");
+ assertThat(msSql.getValidationQuery()).isEqualTo("SELECT 1");
+ }
+
+ @Test
+ public void msSql_does_supportMigration() {
+ assertThat(msSql.supportsMigration()).isTrue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/MySqlTest.java b/sonar-db/src/test/java/org/sonar/db/dialect/MySqlTest.java
new file mode 100644
index 00000000000..c33aa898cb5
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dialect/MySqlTest.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MySqlTest {
+
+ private MySql mySql = new MySql();
+
+ @Test
+ public void matchesJdbcURL() {
+ assertThat(mySql.matchesJdbcURL("jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8")).isTrue();
+ assertThat(mySql.matchesJdbcURL("JDBC:MYSQL://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8")).isTrue();
+
+ assertThat(mySql.matchesJdbcURL("jdbc:hsql:foo")).isFalse();
+ assertThat(mySql.matchesJdbcURL("jdbc:oracle:foo")).isFalse();
+ }
+
+ @Test
+ public void testBooleanSqlValues() {
+ assertThat(mySql.getTrueSqlValue()).isEqualTo("true");
+ assertThat(mySql.getFalseSqlValue()).isEqualTo("false");
+ }
+
+ @Test
+ public void should_configure() {
+ assertThat(mySql.getId()).isEqualTo("mysql");
+ assertThat(mySql.getActiveRecordDialectCode()).isEqualTo("mysql");
+ assertThat(mySql.getDefaultDriverClassName()).isEqualTo("com.mysql.jdbc.Driver");
+ assertThat(mySql.getValidationQuery()).isEqualTo("SELECT 1");
+ }
+
+ @Test
+ public void testFetchSizeForScrolling() throws Exception {
+ assertThat(mySql.getScrollDefaultFetchSize()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(mySql.getScrollSingleRowFetchSize()).isEqualTo(Integer.MIN_VALUE);
+ }
+
+ @Test
+ public void mysql_does_supportMigration() {
+ assertThat(mySql.supportsMigration()).isTrue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/OracleTest.java b/sonar-db/src/test/java/org/sonar/db/dialect/OracleTest.java
new file mode 100644
index 00000000000..cc40aab20e1
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dialect/OracleTest.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OracleTest {
+
+ Oracle dialect = new Oracle();
+
+ @Test
+ public void matchesJdbcURL() {
+ assertThat(dialect.matchesJdbcURL("jdbc:oracle:thin:@localhost/XE")).isTrue();
+ assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse();
+ }
+
+ @Test
+ public void testBooleanSqlValues() {
+ assertThat(dialect.getTrueSqlValue()).isEqualTo("1");
+ assertThat(dialect.getFalseSqlValue()).isEqualTo("0");
+ }
+
+ @Test
+ public void should_configure() {
+ assertThat(dialect.getId()).isEqualTo("oracle");
+ assertThat(dialect.getActiveRecordDialectCode()).isEqualTo("oracle");
+ assertThat(dialect.getDefaultDriverClassName()).isEqualTo("oracle.jdbc.OracleDriver");
+ assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1 FROM DUAL");
+ }
+
+ @Test
+ public void testFetchSizeForScrolling() throws Exception {
+ assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200);
+ assertThat(dialect.getScrollSingleRowFetchSize()).isEqualTo(1);
+ }
+
+ @Test
+ public void oracle_does_supportMigration() {
+ assertThat(dialect.supportsMigration()).isTrue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java b/sonar-db/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java
new file mode 100644
index 00000000000..43cf2d0094f
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.dialect;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PostgreSqlTest {
+
+ PostgreSql dialect = new PostgreSql();
+
+ @Test
+ public void matchesJdbcURL() {
+ assertThat(dialect.matchesJdbcURL("jdbc:postgresql://localhost/sonar")).isTrue();
+ assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse();
+ }
+
+ @Test
+ public void should_set_connection_properties() {
+ assertThat(dialect.getConnectionInitStatements()).isEqualTo(PostgreSql.INIT_STATEMENTS);
+ }
+
+ @Test
+ public void testBooleanSqlValues() {
+ assertThat(dialect.getTrueSqlValue()).isEqualTo("true");
+ assertThat(dialect.getFalseSqlValue()).isEqualTo("false");
+ }
+
+ @Test
+ public void should_configure() {
+ assertThat(dialect.getId()).isEqualTo("postgresql");
+ assertThat(dialect.getActiveRecordDialectCode()).isEqualTo("postgre");
+ assertThat(dialect.getDefaultDriverClassName()).isEqualTo("org.postgresql.Driver");
+ assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1");
+ }
+
+ @Test
+ public void testFetchSizeForScrolling() throws Exception {
+ assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200);
+ }
+
+ @Test
+ public void postgres_does_supportMigration() {
+ assertThat(dialect.supportsMigration()).isTrue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java b/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java
new file mode 100644
index 00000000000..32705a7e5b1
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/duplication/DuplicationDaoTest.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.duplication;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class DuplicationDaoTest extends AbstractDaoTestCase {
+
+ private DuplicationDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new DuplicationDao(getMyBatis());
+ }
+
+ @Test
+ public void shouldGetByHash() {
+ setupData("shouldGetByHash");
+
+ List<DuplicationUnitDto> blocks = dao.selectCandidates(10, 7, "java");
+ assertThat(blocks.size(), is(1));
+
+ DuplicationUnitDto block = blocks.get(0);
+ assertThat("block resourceId", block.getResourceKey(), is("bar-last"));
+ assertThat("block hash", block.getHash(), is("aa"));
+ assertThat("block index in file", block.getIndexInFile(), is(0));
+ assertThat("block start line", block.getStartLine(), is(1));
+ assertThat("block end line", block.getEndLine(), is(2));
+
+ // check null for lastSnapshotId
+ blocks = dao.selectCandidates(10, null, "java");
+ assertThat(blocks.size(), is(2));
+ }
+
+ @Test
+ public void shouldInsert() {
+ setupData("shouldInsert");
+
+ dao.insert(Arrays.asList(new DuplicationUnitDto(1, 2, "bb", 0, 1, 2)));
+
+ checkTables("shouldInsert", "duplications_index");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/event/EventDtoTest.java b/sonar-db/src/test/java/org/sonar/db/event/EventDtoTest.java
new file mode 100644
index 00000000000..cf90cce47cd
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/event/EventDtoTest.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.event;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EventDtoTest {
+
+ @Test
+ public void test_getters_and_setters() throws Exception {
+ EventDto dto = new EventDto()
+ .setId(1L)
+ .setName("1.0")
+ .setCategory("Version")
+ .setDescription("Version 1.0")
+ .setData("some data")
+ .setDate(1413407091086L)
+ .setComponentUuid("ABCD")
+ .setSnapshotId(1000L)
+ .setCreatedAt(1225630680000L);
+
+ assertThat(dto.getId()).isEqualTo(1L);
+ assertThat(dto.getName()).isEqualTo("1.0");
+ assertThat(dto.getCategory()).isEqualTo("Version");
+ assertThat(dto.getDescription()).isEqualTo("Version 1.0");
+ assertThat(dto.getData()).isEqualTo("some data");
+ assertThat(dto.getComponentUuid()).isEqualTo("ABCD");
+ assertThat(dto.getSnapshotId()).isEqualTo(1000L);
+ assertThat(dto.getCreatedAt()).isEqualTo(1225630680000L);
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/ActionPlanDaoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/ActionPlanDaoTest.java
new file mode 100644
index 00000000000..939fc01ed36
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/ActionPlanDaoTest.java
@@ -0,0 +1,122 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ActionPlanDaoTest extends AbstractDaoTestCase {
+
+ ActionPlanDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new ActionPlanDao(getMyBatis());
+ }
+
+ @Test
+ public void should_insert_new_action_plan() {
+ ActionPlanDto actionPlanDto = new ActionPlanDto().setKey("ABC").setName("Long term").setDescription("Long term action plan").setStatus("OPEN")
+ .setProjectId(1l).setUserLogin("arthur");
+
+ dao.save(actionPlanDto);
+
+ checkTables("should_insert_new_action_plan", new String[] {"id", "created_at", "updated_at"}, "action_plans");
+ }
+
+ @Test
+ public void should_update_action_plan() {
+ setupData("should_update_action_plan");
+
+ ActionPlanDto actionPlanDto = new ActionPlanDto().setKey("ABC").setName("Long term").setDescription("Long term action plan").setStatus("OPEN")
+ .setProjectId(1l).setUserLogin("arthur");
+ dao.update(actionPlanDto);
+
+ checkTables("should_update_action_plan", new String[] {"id", "created_at", "updated_at"}, "action_plans");
+ }
+
+ @Test
+ public void should_delete_action_plan() {
+ setupData("should_delete_action_plan");
+
+ dao.delete("BCD");
+
+ checkTables("should_delete_action_plan", new String[] {"id", "created_at", "updated_at"}, "action_plans");
+ }
+
+ @Test
+ public void should_find_by_key() {
+ setupData("shared", "should_find_by_key");
+
+ ActionPlanDto result = dao.findByKey("ABC");
+ assertThat(result).isNotNull();
+ assertThat(result.getKey()).isEqualTo("ABC");
+ assertThat(result.getProjectKey()).isEqualTo("org.sonar.Sample");
+ }
+
+ @Test
+ public void should_find_by_keys() {
+ setupData("shared", "should_find_by_keys");
+
+ Collection<ActionPlanDto> result = dao.findByKeys(newArrayList("ABC", "ABD", "ABE"));
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void should_find_by_keys_on_huge_number_of_keys() {
+ setupData("shared");
+
+ SqlSession session = getMyBatis().openSession();
+ List<String> hugeNbOKeys = newArrayList();
+ for (int i = 0; i < 4500; i++) {
+ hugeNbOKeys.add("ABCD" + i);
+ }
+ List<ActionPlanDto> result = dao.findByKeys(hugeNbOKeys);
+ MyBatis.closeQuietly(session);
+
+ // The goal of this test is only to check that the query do no fail, not to check the number of results
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void should_find_open_by_project_id() {
+ setupData("shared", "should_find_open_by_project_id");
+
+ Collection<ActionPlanDto> result = dao.findOpenByProjectId(1l);
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ public void should_find_by_name_and_project_id() {
+ setupData("shared", "should_find_by_name_and_project_id");
+
+ Collection<ActionPlanDto> result = dao.findByNameAndProjectId("SHORT_TERM", 1l);
+ assertThat(result).hasSize(2);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/ActionPlanStatsDaoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/ActionPlanStatsDaoTest.java
new file mode 100644
index 00000000000..a70e1a3d08e
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/ActionPlanStatsDaoTest.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ActionPlanStatsDaoTest extends AbstractDaoTestCase {
+
+ ActionPlanStatsDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new ActionPlanStatsDao(getMyBatis());
+ }
+
+ @Test
+ public void should_find_by_project() {
+ setupData("shared", "should_find_by_project");
+
+ Collection<ActionPlanStatsDto> result = dao.findByProjectId(1l);
+ assertThat(result).isNotEmpty();
+
+ ActionPlanStatsDto actionPlanStatsDto = result.iterator().next();
+ assertThat(actionPlanStatsDto.getProjectKey()).isEqualTo("org.sonar.Sample");
+ assertThat(actionPlanStatsDto.getTotalIssues()).isEqualTo(3);
+ assertThat(actionPlanStatsDto.getUnresolvedIssues()).isEqualTo(1);
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDaoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDaoTest.java
new file mode 100644
index 00000000000..4510995aace
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDaoTest.java
@@ -0,0 +1,197 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class IssueChangeDaoTest extends AbstractDaoTestCase {
+
+ DbSession session;
+
+ IssueChangeDao dao;
+
+ @Before
+ public void createDao() {
+ session = getMyBatis().openSession(false);
+ dao = new IssueChangeDao(getMyBatis());
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void select_comments_by_issues() {
+ setupData("shared");
+
+ DbSession session = getMyBatis().openSession(false);
+ List<DefaultIssueComment> comments = dao.selectCommentsByIssues(session, Arrays.asList("1000"));
+ MyBatis.closeQuietly(session);
+ assertThat(comments).hasSize(2);
+
+ // chronological order
+ DefaultIssueComment first = comments.get(0);
+ assertThat(first.markdownText()).isEqualTo("old comment");
+
+ DefaultIssueComment second = comments.get(1);
+ assertThat(second.userLogin()).isEqualTo("arthur");
+ assertThat(second.key()).isEqualTo("FGHIJ");
+ assertThat(second.markdownText()).isEqualTo("recent comment");
+ }
+
+ @Test
+ public void select_comments_by_issues_on_huge_number_of_issues() {
+ setupData("shared");
+
+ DbSession session = getMyBatis().openSession(false);
+ List<String> hugeNbOfIssues = newArrayList();
+ for (int i = 0; i < 4500; i++) {
+ hugeNbOfIssues.add("ABCD" + i);
+ }
+ List<DefaultIssueComment> comments = dao.selectCommentsByIssues(session, hugeNbOfIssues);
+ MyBatis.closeQuietly(session);
+
+ // The goal of this test is only to check that the query do no fail, not to check the number of results
+ assertThat(comments).isEmpty();
+ }
+
+ @Test
+ public void select_comment_by_key() {
+ setupData("shared");
+
+ DefaultIssueComment comment = dao.selectCommentByKey("FGHIJ");
+ assertThat(comment).isNotNull();
+ assertThat(comment.key()).isEqualTo("FGHIJ");
+ assertThat(comment.key()).isEqualTo("FGHIJ");
+ assertThat(comment.userLogin()).isEqualTo("arthur");
+
+ assertThat(dao.selectCommentByKey("UNKNOWN")).isNull();
+ }
+
+ @Test
+ public void select_issue_changelog_from_issue_key() {
+ setupData("shared");
+
+ List<FieldDiffs> changelog = dao.selectChangelogByIssue("1000");
+ assertThat(changelog).hasSize(1);
+ assertThat(changelog.get(0).diffs()).hasSize(1);
+ assertThat(changelog.get(0).diffs().get("severity").newValue()).isEqualTo("BLOCKER");
+ assertThat(changelog.get(0).diffs().get("severity").oldValue()).isEqualTo("MAJOR");
+ }
+
+ @Test
+ public void selectChangelogOfNonClosedIssuesByComponent() {
+ setupData("selectChangelogOfNonClosedIssuesByComponent");
+
+ List<IssueChangeDto> dtos = dao.selectChangelogOfNonClosedIssuesByComponent("FILE_1");
+ assertThat(dtos).extracting("id").containsExactly(100L, 103L);
+ }
+
+ @Test
+ public void select_comments_by_issues_empty_input() {
+ // no need to connect to db
+ DbSession session = mock(DbSession.class);
+ List<DefaultIssueComment> comments = dao.selectCommentsByIssues(session, Collections.<String>emptyList());
+
+ assertThat(comments).isEmpty();
+ }
+
+ @Test
+ public void delete() {
+ setupData("delete");
+
+ assertThat(dao.delete("COMMENT-2")).isTrue();
+
+ checkTable("delete", "issue_changes");
+ }
+
+ @Test
+ public void delete_unknown_key() {
+ setupData("delete");
+
+ assertThat(dao.delete("UNKNOWN")).isFalse();
+ }
+
+ @Test
+ public void insert() {
+ setupData("empty");
+
+ IssueChangeDto changeDto = new IssueChangeDto()
+ .setKey("EFGH")
+ .setUserLogin("emmerik")
+ .setChangeData("Some text")
+ .setChangeType("comment")
+ .setIssueKey("ABCDE")
+ .setCreatedAt(1_500_000_000_000L)
+ .setUpdatedAt(1_501_000_000_000L)
+ .setIssueChangeCreationDate(1_502_000_000_000L);
+
+ dao.insert(session, changeDto);
+ session.commit();
+
+ checkTable("insert", "issue_changes");
+ }
+
+ @Test
+ public void update() {
+ setupData("update");
+
+ IssueChangeDto change = new IssueChangeDto();
+ change.setKey("COMMENT-2");
+
+ // Only the following fields can be updated:
+ change.setChangeData("new comment");
+ change.setUpdatedAt(1_500_000_000_000L);
+
+ assertThat(dao.update(change)).isTrue();
+
+ checkTable("update", "issue_changes");
+ }
+
+ @Test
+ public void update_unknown_key() {
+ setupData("update");
+
+ IssueChangeDto change = new IssueChangeDto();
+ change.setKey("UNKNOWN");
+
+ // Only the following fields can be updated:
+ change.setChangeData("new comment");
+ change.setUpdatedAt(DateUtils.parseDate("2013-06-30").getTime());
+
+ assertThat(dao.update(change)).isFalse();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDtoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDtoTest.java
new file mode 100644
index 00000000000..2d7cfdcb478
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeDtoTest.java
@@ -0,0 +1,141 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.utils.DateUtils.parseDate;
+
+public class IssueChangeDtoTest {
+
+ @Test
+ public void create_from_comment() {
+ DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+
+ IssueChangeDto dto = IssueChangeDto.of(comment);
+
+ assertThat(dto.getChangeData()).isEqualTo("the comment");
+ assertThat(dto.getChangeType()).isEqualTo("comment");
+ assertThat(dto.getCreatedAt()).isNotNull();
+ assertThat(dto.getUpdatedAt()).isNotNull();
+ assertThat(dto.getIssueChangeCreationDate()).isNotNull();
+ assertThat(dto.getIssueKey()).isEqualTo("ABCDE");
+ assertThat(dto.getUserLogin()).isEqualTo("emmerik");
+ }
+
+ @Test
+ public void create_from_comment_with_created_at() {
+ DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+ comment.setCreatedAt(parseDate("2015-01-13"));
+
+ IssueChangeDto dto = IssueChangeDto.of(comment);
+
+ assertThat(dto.getIssueChangeCreationDate()).isEqualTo(parseDate("2015-01-13").getTime());
+ }
+
+ @Test
+ public void create_from_diff() {
+ FieldDiffs diffs = new FieldDiffs();
+ diffs.setDiff("severity", "INFO", "BLOCKER");
+ diffs.setUserLogin("emmerik");
+
+ IssueChangeDto dto = IssueChangeDto.of("ABCDE", diffs);
+
+ assertThat(dto.getChangeData()).isEqualTo("severity=INFO|BLOCKER");
+ assertThat(dto.getChangeType()).isEqualTo("diff");
+ assertThat(dto.getCreatedAt()).isNotNull();
+ assertThat(dto.getUpdatedAt()).isNotNull();
+ assertThat(dto.getIssueChangeCreationDate()).isNull();
+ assertThat(dto.getIssueKey()).isEqualTo("ABCDE");
+ assertThat(dto.getUserLogin()).isEqualTo("emmerik");
+ }
+
+ @Test
+ public void create_from_diff_with_created_at() {
+ FieldDiffs diffs = new FieldDiffs();
+ diffs.setDiff("severity", "INFO", "BLOCKER");
+ diffs.setUserLogin("emmerik");
+ diffs.setCreationDate(parseDate("2015-01-13"));
+
+ IssueChangeDto dto = IssueChangeDto.of("ABCDE", diffs);
+
+ assertThat(dto.getIssueChangeCreationDate()).isEqualTo(parseDate("2015-01-13").getTime());
+ }
+
+ @Test
+ public void to_comment() {
+ IssueChangeDto changeDto = new IssueChangeDto()
+ .setKey("EFGH")
+ .setUserLogin("emmerik")
+ .setChangeData("Some text")
+ .setIssueKey("ABCDE")
+ .setCreatedAt(System2.INSTANCE.now())
+ .setUpdatedAt(System2.INSTANCE.now());
+
+ DefaultIssueComment comment = changeDto.toComment();
+ assertThat(comment.key()).isEqualTo("EFGH");
+ assertThat(comment.markdownText()).isEqualTo("Some text");
+ assertThat(comment.createdAt()).isNotNull();
+ assertThat(comment.updatedAt()).isNotNull();
+ assertThat(comment.userLogin()).isEqualTo("emmerik");
+ assertThat(comment.issueKey()).isEqualTo("ABCDE");
+ }
+
+ @Test
+ public void to_field_diffs_with_issue_creation_date() {
+ IssueChangeDto changeDto = new IssueChangeDto()
+ .setKey("EFGH")
+ .setUserLogin("emmerik")
+ .setChangeData("Some text")
+ .setIssueKey("ABCDE")
+ .setIssueChangeCreationDate(System2.INSTANCE.now());
+
+ FieldDiffs diffs = changeDto.toFieldDiffs();
+ assertThat(diffs.userLogin()).isEqualTo("emmerik");
+ assertThat(diffs.issueKey()).isEqualTo("ABCDE");
+ assertThat(diffs.creationDate()).isNotNull();
+ }
+
+ @Test
+ public void to_field_diffs_with_create_at() {
+ IssueChangeDto changeDto = new IssueChangeDto()
+ .setKey("EFGH")
+ .setUserLogin("emmerik")
+ .setChangeData("Some text")
+ .setIssueKey("ABCDE")
+ .setCreatedAt(System2.INSTANCE.now());
+
+ FieldDiffs diffs = changeDto.toFieldDiffs();
+ assertThat(diffs.userLogin()).isEqualTo("emmerik");
+ assertThat(diffs.issueKey()).isEqualTo("ABCDE");
+ assertThat(diffs.creationDate()).isNotNull();
+ }
+
+ @Test
+ public void to_string() {
+ DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
+ IssueChangeDto dto = IssueChangeDto.of(comment);
+ assertThat(dto.toString()).contains("ABCDE");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeMapperTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeMapperTest.java
new file mode 100644
index 00000000000..dc2fef893e6
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueChangeMapperTest.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.MyBatis;
+
+public class IssueChangeMapperTest extends AbstractDaoTestCase {
+ SqlSession session;
+ IssueChangeMapper mapper;
+
+ @Before
+ public void setUp() {
+ session = getMyBatis().openSession();
+ mapper = session.getMapper(IssueChangeMapper.class);
+ }
+
+ @After
+ public void tearDown() {
+ MyBatis.closeQuietly(session);
+ }
+
+ @Test
+ public void insert_diff() {
+ IssueChangeDto dto = new IssueChangeDto();
+ dto.setKey(null /* no key on field changes */);
+ dto.setUserLogin("emmerik");
+ dto.setIssueKey("ABCDE");
+ dto.setChangeType(IssueChangeDto.TYPE_FIELD_CHANGE);
+ dto.setChangeData("severity=INFO|BLOCKER");
+ dto.setCreatedAt(1_500_000_000_000L);
+ dto.setUpdatedAt(1_500_000_000_000L);
+ dto.setIssueChangeCreationDate(1_500_000_000_000L);
+ mapper.insert(dto);
+ session.commit();
+
+ checkTables("insert_diff", new String[] {"id"}, "issue_changes");
+ }
+
+ @Test
+ public void insert_comment() {
+ IssueChangeDto dto = new IssueChangeDto();
+ dto.setKey("COMMENT-1234");
+ dto.setUserLogin("emmerik");
+ dto.setIssueKey("ABCDE");
+ dto.setChangeType(IssueChangeDto.TYPE_COMMENT);
+ dto.setChangeData("the comment");
+ dto.setCreatedAt(1_500_000_000_000L);
+ dto.setUpdatedAt(1_500_000_000_000L);
+ mapper.insert(dto);
+ session.commit();
+
+ checkTables("insert_comment", new String[] {"id"}, "issue_changes");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueDaoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueDaoTest.java
new file mode 100644
index 00000000000..24f556df1e6
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueDaoTest.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import org.apache.ibatis.executor.result.DefaultResultHandler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueDaoTest extends AbstractDaoTestCase {
+
+ DbSession session;
+
+ IssueDao dao;
+
+ @Before
+ public void createDao() {
+ session = getMyBatis().openSession(false);
+ dao = new IssueDao(getMyBatis());
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void select_non_closed_issues_by_module() {
+ setupData("shared", "should_select_non_closed_issues_by_module");
+
+ // 400 is a non-root module, we should find 2 issues from classes and one on itself
+ DefaultResultHandler handler = new DefaultResultHandler();
+ dao.selectNonClosedIssuesByModule(400, handler);
+ assertThat(handler.getResultList()).hasSize(3);
+
+ IssueDto issue = (IssueDto) handler.getResultList().get(0);
+ assertThat(issue.getRuleRepo()).isNotNull();
+ assertThat(issue.getRule()).isNotNull();
+ assertThat(issue.getComponentKey()).isNotNull();
+ assertThat(issue.getProjectKey()).isEqualTo("struts");
+
+ // 399 is the root module, we should only find 1 issue on itself
+ handler = new DefaultResultHandler();
+ dao.selectNonClosedIssuesByModule(399, handler);
+ assertThat(handler.getResultList()).hasSize(1);
+
+ issue = (IssueDto) handler.getResultList().get(0);
+ assertThat(issue.getComponentKey()).isEqualTo("struts");
+ assertThat(issue.getProjectKey()).isEqualTo("struts");
+ }
+
+ /**
+ * SONAR-5218
+ */
+ @Test
+ public void select_non_closed_issues_by_module_on_removed_project() {
+ // All issues are linked on a project that is not existing anymore
+
+ setupData("shared", "should_select_non_closed_issues_by_module_on_removed_project");
+
+ // 400 is a non-root module, we should find 2 issues from classes and one on itself
+ DefaultResultHandler handler = new DefaultResultHandler();
+ dao.selectNonClosedIssuesByModule(400, handler);
+ assertThat(handler.getResultList()).hasSize(3);
+
+ IssueDto issue = (IssueDto) handler.getResultList().get(0);
+ assertThat(issue.getRuleRepo()).isNotNull();
+ assertThat(issue.getRule()).isNotNull();
+ assertThat(issue.getComponentKey()).isNotNull();
+ assertThat(issue.getProjectKey()).isNull();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueDtoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueDtoTest.java
new file mode 100644
index 00000000000..3933f0302e3
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueDtoTest.java
@@ -0,0 +1,150 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.utils.Duration;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.rule.RuleDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueDtoTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void set_data_check_maximal_length() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Issue attributes must not exceed 4000 characters: ");
+
+ StringBuilder s = new StringBuilder(4500);
+ for (int i = 0; i < 4500; i++) {
+ s.append('a');
+ }
+ new IssueDto().setIssueAttributes(s.toString());
+ }
+
+ @Test
+ public void set_issue_fields() {
+ Date createdAt = DateUtils.addDays(new Date(), -5);
+ Date updatedAt = DateUtils.addDays(new Date(), -3);
+ Date closedAt = DateUtils.addDays(new Date(), -1);
+
+ IssueDto dto = new IssueDto()
+ .setKee("100")
+ .setRuleId(1)
+ .setRuleKey("squid", "AvoidCycle")
+ .setLanguage("xoo")
+ .setComponentKey("org.sonar.sample:Sample")
+ .setComponentUuid("CDEF")
+ .setProjectUuid("GHIJ")
+ .setModuleUuid("BCDE")
+ .setModuleUuidPath("ABCD.BCDE.")
+ .setProjectKey("org.sonar.sample")
+ .setStatus(Issue.STATUS_CLOSED)
+ .setResolution(Issue.RESOLUTION_FALSE_POSITIVE)
+ .setEffortToFix(15.0)
+ .setDebt(10L)
+ .setLine(6)
+ .setSeverity("BLOCKER")
+ .setMessage("message")
+ .setManualSeverity(true)
+ .setReporter("arthur")
+ .setAssignee("perceval")
+ .setIssueAttributes("key=value")
+ .setAuthorLogin("pierre")
+ .setIssueCreationDate(createdAt)
+ .setIssueUpdateDate(updatedAt)
+ .setIssueCloseDate(closedAt);
+
+ DefaultIssue issue = dto.toDefaultIssue();
+ assertThat(issue.key()).isEqualTo("100");
+ assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
+ assertThat(issue.language()).isEqualTo("xoo");
+ assertThat(issue.componentUuid()).isEqualTo("CDEF");
+ assertThat(issue.projectUuid()).isEqualTo("GHIJ");
+ assertThat(issue.componentKey()).isEqualTo("org.sonar.sample:Sample");
+ assertThat(issue.moduleUuid()).isEqualTo("BCDE");
+ assertThat(issue.moduleUuidPath()).isEqualTo("ABCD.BCDE.");
+ assertThat(issue.projectKey()).isEqualTo("org.sonar.sample");
+ assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
+ assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FALSE_POSITIVE);
+ assertThat(issue.effortToFix()).isEqualTo(15.0);
+ assertThat(issue.debt()).isEqualTo(Duration.create(10L));
+ assertThat(issue.line()).isEqualTo(6);
+ assertThat(issue.severity()).isEqualTo("BLOCKER");
+ assertThat(issue.message()).isEqualTo("message");
+ assertThat(issue.manualSeverity()).isTrue();
+ assertThat(issue.reporter()).isEqualTo("arthur");
+ assertThat(issue.assignee()).isEqualTo("perceval");
+ assertThat(issue.attribute("key")).isEqualTo("value");
+ assertThat(issue.authorLogin()).isEqualTo("pierre");
+ assertThat(issue.creationDate()).isEqualTo(DateUtils.truncate(createdAt, Calendar.SECOND));
+ assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(updatedAt, Calendar.SECOND));
+ assertThat(issue.closeDate()).isEqualTo(DateUtils.truncate(closedAt, Calendar.SECOND));
+ assertThat(issue.isNew()).isFalse();
+ }
+
+ @Test
+ public void set_rule() {
+ IssueDto dto = new IssueDto()
+ .setKee("100")
+ .setRule(new RuleDto().setId(1).setRuleKey("AvoidCycle").setRepositoryKey("squid"))
+ .setLanguage("xoo");
+
+ assertThat(dto.getRuleId()).isEqualTo(1);
+ assertThat(dto.getRuleRepo()).isEqualTo("squid");
+ assertThat(dto.getRule()).isEqualTo("AvoidCycle");
+ assertThat(dto.getRuleKey().toString()).isEqualTo("squid:AvoidCycle");
+ assertThat(dto.getLanguage()).isEqualTo("xoo");
+ }
+
+ @Test
+ public void set_tags() {
+ IssueDto dto = new IssueDto();
+ assertThat(dto.getTags()).isEmpty();
+ assertThat(dto.getTagsString()).isNull();
+
+ dto.setTags(Arrays.asList("tag1", "tag2", "tag3"));
+ assertThat(dto.getTags()).containsOnly("tag1", "tag2", "tag3");
+ assertThat(dto.getTagsString()).isEqualTo("tag1,tag2,tag3");
+
+ dto.setTags(Arrays.<String>asList());
+ assertThat(dto.getTags()).isEmpty();
+
+ dto.setTagsString("tag1, tag2 ,,tag3");
+ assertThat(dto.getTags()).containsOnly("tag1", "tag2", "tag3");
+
+ dto.setTagsString(null);
+ assertThat(dto.getTags()).isEmpty();
+
+ dto.setTagsString("");
+ assertThat(dto.getTags()).isEmpty();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueFilterDaoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueFilterDaoTest.java
new file mode 100644
index 00000000000..474eb8e8770
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueFilterDaoTest.java
@@ -0,0 +1,129 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueFilterDaoTest extends AbstractDaoTestCase {
+
+ IssueFilterDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new IssueFilterDao(getMyBatis());
+ }
+
+ @Test
+ public void should_select_by_id() {
+ setupData("shared");
+
+ IssueFilterDto filter = dao.selectById(1L);
+
+ assertThat(filter.getId()).isEqualTo(1L);
+ assertThat(filter.getName()).isEqualTo("Sonar Issues");
+ assertThat(filter.isShared()).isTrue();
+
+ assertThat(dao.selectById(123L)).isNull();
+ }
+
+ @Test
+ public void should_select_by_user() {
+ setupData("should_select_by_user");
+
+ List<IssueFilterDto> results = dao.selectByUser("michael");
+
+ assertThat(results).hasSize(2);
+ }
+
+ @Test
+ public void should_select_by_user_with_only_favorite_filters() {
+ setupData("should_select_by_user_with_only_favorite_filters");
+
+ List<IssueFilterDto> results = dao.selectFavoriteFiltersByUser("michael");
+
+ assertThat(results).hasSize(1);
+ IssueFilterDto issueFilterDto = results.get(0);
+ assertThat(issueFilterDto.getId()).isEqualTo(2L);
+ }
+
+ @Test
+ public void should_select_shared() {
+ setupData("shared");
+
+ assertThat(dao.selectSharedFilters()).hasSize(1);
+ }
+
+ @Test
+ public void should_select_provided_by_name() {
+ setupData("should_select_provided_by_name");
+
+ assertThat(dao.selectProvidedFilterByName("Unresolved Issues").getName()).isEqualTo("Unresolved Issues");
+ assertThat(dao.selectProvidedFilterByName("My Unresolved Issues").getName()).isEqualTo("My Unresolved Issues");
+ assertThat(dao.selectProvidedFilterByName("Unknown Filter")).isNull();
+ }
+
+ @Test
+ public void should_insert() {
+ setupData("shared");
+
+ IssueFilterDto filterDto = new IssueFilterDto();
+ filterDto.setName("Sonar Open issues");
+ filterDto.setUserLogin("michael");
+ filterDto.setShared(true);
+ filterDto.setDescription("All open issues on Sonar");
+ filterDto.setData("statuses=OPEN|componentRoots=org.codehaus.sonar");
+
+ dao.insert(filterDto);
+
+ checkTables("should_insert", new String[] {"created_at", "updated_at"}, "issue_filters");
+ }
+
+ @Test
+ public void should_update() {
+ setupData("shared");
+
+ IssueFilterDto filterDto = new IssueFilterDto();
+ filterDto.setId(2L);
+ filterDto.setName("Closed issues");
+ filterDto.setShared(false);
+ filterDto.setDescription("All closed issues");
+ filterDto.setData("statuses=CLOSED");
+ filterDto.setUserLogin("bernard");
+
+ dao.update(filterDto);
+
+ checkTables("should_update", new String[] {"created_at", "updated_at"}, "issue_filters");
+ }
+
+ @Test
+ public void should_delete() {
+ setupData("shared");
+
+ dao.delete(1l);
+
+ checkTables("should_delete", new String[] {"created_at", "updated_at"}, "issue_filters");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueFilterFavouriteDaoTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueFilterFavouriteDaoTest.java
new file mode 100644
index 00000000000..abe79583e69
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueFilterFavouriteDaoTest.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueFilterFavouriteDaoTest extends AbstractDaoTestCase {
+
+ IssueFilterFavouriteDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new IssueFilterFavouriteDao(getMyBatis());
+ }
+
+ @Test
+ public void should_select_by_id() {
+ setupData("shared");
+
+ IssueFilterFavouriteDto dto = dao.selectById(1L);
+ assertThat(dto.getId()).isEqualTo(1L);
+ assertThat(dto.getUserLogin()).isEqualTo("stephane");
+ assertThat(dto.getIssueFilterId()).isEqualTo(10L);
+ assertThat(dto.getCreatedAt()).isNotNull();
+
+ assertThat(dao.selectById(999L)).isNull();
+ }
+
+ @Test
+ public void should_select_by_filter_id() {
+ setupData("shared");
+
+ List<IssueFilterFavouriteDto> dtos = dao.selectByFilterId(11L);
+ assertThat(dtos).hasSize(1);
+ IssueFilterFavouriteDto dto = dtos.get(0);
+ assertThat(dto.getId()).isEqualTo(2L);
+ assertThat(dto.getUserLogin()).isEqualTo("stephane");
+ assertThat(dto.getIssueFilterId()).isEqualTo(11L);
+ assertThat(dto.getCreatedAt()).isNotNull();
+
+ assertThat(dao.selectByFilterId(10L)).hasSize(2);
+ assertThat(dao.selectByFilterId(999L)).isEmpty();
+ }
+
+ @Test
+ public void should_insert() {
+ setupData("shared");
+
+ IssueFilterFavouriteDto dto = new IssueFilterFavouriteDto();
+ dto.setUserLogin("arthur");
+ dto.setIssueFilterId(11L);
+
+ dao.insert(dto);
+
+ checkTables("should_insert", new String[] {"created_at"}, "issue_filter_favourites");
+ }
+
+ @Test
+ public void should_delete() {
+ setupData("shared");
+
+ dao.delete(3l);
+
+ checkTables("should_delete", new String[] {"created_at"}, "issue_filter_favourites");
+ }
+
+ @Test
+ public void should_delete_by_issue_filter_id() {
+ setupData("shared");
+
+ dao.deleteByFilterId(10l);
+
+ checkTables("should_delete_by_issue_filter_id", new String[] {"created_at"}, "issue_filter_favourites");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/issue/IssueMapperTest.java b/sonar-db/src/test/java/org/sonar/db/issue/IssueMapperTest.java
new file mode 100644
index 00000000000..1e241b783ae
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/issue/IssueMapperTest.java
@@ -0,0 +1,191 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.MyBatis;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueMapperTest extends AbstractDaoTestCase {
+
+ SqlSession session;
+ IssueMapper mapper;
+
+ @Before
+ public void setUp() {
+ session = getMyBatis().openSession();
+ mapper = session.getMapper(IssueMapper.class);
+ }
+
+ @After
+ public void tearDown() {
+ MyBatis.closeQuietly(session);
+ }
+
+ @Test
+ public void insert() {
+ IssueDto dto = new IssueDto();
+ dto.setComponentUuid("uuid-123");
+ dto.setProjectUuid("uuid-100");
+ dto.setRuleId(200);
+ dto.setKee("ABCDE");
+ dto.setLine(500);
+ dto.setEffortToFix(3.14);
+ dto.setDebt(10L);
+ dto.setResolution("FIXED");
+ dto.setStatus("RESOLVED");
+ dto.setSeverity("BLOCKER");
+ dto.setReporter("emmerik");
+ dto.setAuthorLogin("morgan");
+ dto.setAssignee("karadoc");
+ dto.setActionPlanKey("current_sprint");
+ dto.setIssueAttributes("JIRA=FOO-1234");
+ dto.setChecksum("123456789");
+ dto.setMessage("the message");
+
+ dto.setIssueCreationTime(1_401_000_000_000L);
+ dto.setIssueUpdateTime(1_402_000_000_000L);
+ dto.setIssueCloseTime(1_403_000_000_000L);
+ dto.setCreatedAt(1_400_000_000_000L);
+ dto.setUpdatedAt(1_500_000_000_000L);
+
+ mapper.insert(dto);
+ session.commit();
+
+ checkTables("testInsert", new String[] {"id"}, "issues");
+ }
+
+ @Test
+ public void update() {
+ setupData("testUpdate");
+
+ IssueDto dto = new IssueDto();
+ dto.setComponentUuid("uuid-123");
+ dto.setProjectUuid("uuid-101");
+ dto.setRuleId(200);
+ dto.setKee("ABCDE");
+ dto.setLine(500);
+ dto.setEffortToFix(3.14);
+ dto.setDebt(10L);
+ dto.setResolution("FIXED");
+ dto.setStatus("RESOLVED");
+ dto.setSeverity("BLOCKER");
+ dto.setReporter("emmerik");
+ dto.setAuthorLogin("morgan");
+ dto.setAssignee("karadoc");
+ dto.setActionPlanKey("current_sprint");
+ dto.setIssueAttributes("JIRA=FOO-1234");
+ dto.setChecksum("123456789");
+ dto.setMessage("the message");
+
+ dto.setIssueCreationTime(1_401_000_000_000L);
+ dto.setIssueUpdateTime(1_402_000_000_000L);
+ dto.setIssueCloseTime(1_403_000_000_000L);
+ dto.setCreatedAt(1_400_000_000_000L);
+ dto.setUpdatedAt(1_500_000_000_000L);
+
+ mapper.update(dto);
+ session.commit();
+
+ checkTables("testUpdate", new String[] {"id"}, "issues");
+ }
+
+ @Test
+ public void updateBeforeSelectedDate_without_conflict() {
+ setupData("testUpdate");
+
+ IssueDto dto = new IssueDto();
+ dto.setComponentUuid("uuid-123");
+ dto.setProjectUuid("uuid-101");
+ dto.setRuleId(200);
+ dto.setKee("ABCDE");
+ dto.setLine(500);
+ dto.setEffortToFix(3.14);
+ dto.setDebt(10L);
+ dto.setResolution("FIXED");
+ dto.setStatus("RESOLVED");
+ dto.setSeverity("BLOCKER");
+ dto.setReporter("emmerik");
+ dto.setAuthorLogin("morgan");
+ dto.setAssignee("karadoc");
+ dto.setActionPlanKey("current_sprint");
+ dto.setIssueAttributes("JIRA=FOO-1234");
+ dto.setChecksum("123456789");
+ dto.setMessage("the message");
+ dto.setIssueCreationTime(1_401_000_000_000L);
+ dto.setIssueUpdateTime(1_402_000_000_000L);
+ dto.setIssueCloseTime(1_403_000_000_000L);
+ dto.setCreatedAt(1_400_000_000_000L);
+ dto.setUpdatedAt(1_500_000_000_000L);
+
+ // selected after last update -> ok
+ dto.setSelectedAt(1500000000000L);
+
+ int count = mapper.updateIfBeforeSelectedDate(dto);
+ assertThat(count).isEqualTo(1);
+ session.commit();
+
+ checkTables("testUpdate", new String[] {"id"}, "issues");
+ }
+
+ @Test
+ public void updateBeforeSelectedDate_with_conflict() {
+ setupData("updateBeforeSelectedDate_with_conflict");
+
+ IssueDto dto = new IssueDto();
+ dto.setComponentUuid("uuid-123");
+ dto.setProjectUuid("uuid-101");
+ dto.setRuleId(200);
+ dto.setKee("ABCDE");
+ dto.setLine(500);
+ dto.setEffortToFix(3.14);
+ dto.setDebt(10L);
+ dto.setResolution("FIXED");
+ dto.setStatus("RESOLVED");
+ dto.setSeverity("BLOCKER");
+ dto.setReporter("emmerik");
+ dto.setAuthorLogin("morgan");
+ dto.setAssignee("karadoc");
+ dto.setActionPlanKey("current_sprint");
+ dto.setIssueAttributes("JIRA=FOO-1234");
+ dto.setChecksum("123456789");
+ dto.setMessage("the message");
+ dto.setIssueCreationDate(DateUtils.parseDate("2013-05-18"));
+ dto.setIssueUpdateDate(DateUtils.parseDate("2013-05-19"));
+ dto.setIssueCloseDate(DateUtils.parseDate("2013-05-20"));
+ dto.setCreatedAt(1400000000000L);
+ dto.setUpdatedAt(1460000000000L);
+
+ // selected before last update -> ko
+ dto.setSelectedAt(1400000000000L);
+
+ int count = mapper.updateIfBeforeSelectedDate(dto);
+ assertThat(count).isEqualTo(0);
+ session.commit();
+
+ checkTables("updateBeforeSelectedDate_with_conflict", new String[] {"id"}, "issues");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest.java b/sonar-db/src/test/java/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest.java
new file mode 100644
index 00000000000..13253d6bef3
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.loadedtemplate;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class LoadedTemplateDaoTest extends AbstractDaoTestCase {
+
+ private LoadedTemplateDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new LoadedTemplateDao(getMyBatis());
+ }
+
+ @Test
+ public void shouldCountByTypeAndKey() {
+ setupData("shouldCountByTypeAndKey");
+ assertThat(dao.countByTypeAndKey("DASHBOARD", "HOTSPOTS"), is(1));
+ assertThat(dao.countByTypeAndKey("DASHBOARD", "UNKNOWN"), is(0));
+ assertThat(dao.countByTypeAndKey("PROFILE", "HOTSPOTS"), is(0));
+ }
+
+ @Test
+ public void shouldInsert() {
+ setupData("shouldInsert");
+
+ LoadedTemplateDto template = new LoadedTemplateDto("SQALE", "DASHBOARD");
+ dao.insert(template);
+
+ checkTables("shouldInsert", "loaded_templates");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/measure/MeasureDtoTest.java b/sonar-db/src/test/java/org/sonar/db/measure/MeasureDtoTest.java
new file mode 100644
index 00000000000..058b0007c6c
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/measure/MeasureDtoTest.java
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import com.google.common.base.Strings;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MeasureDtoTest {
+
+ MeasureDto sut = new MeasureDto();
+
+ @Test
+ public void test_getter_and_setter() throws Exception {
+ sut
+ .setComponentKey("component")
+ .setMetricKey("metric")
+ .setId(10L)
+ .setValue(2d)
+ .setData("text value")
+ .setVariation(1, 1d)
+ .setVariation(2, 2d)
+ .setVariation(3, 3d)
+ .setVariation(4, 4d)
+ .setVariation(5, 5d);
+
+ assertThat(sut.getId()).isEqualTo(10);
+ assertThat(sut.getValue()).isEqualTo(2d);
+ assertThat(sut.getData()).isNotNull();
+ assertThat(sut.getVariation(1)).isEqualTo(1d);
+ assertThat(sut.getVariation(2)).isEqualTo(2d);
+ assertThat(sut.getVariation(3)).isEqualTo(3d);
+ assertThat(sut.getVariation(4)).isEqualTo(4d);
+ assertThat(sut.getVariation(5)).isEqualTo(5d);
+ }
+
+ @Test
+ public void value_with_text_over_4000_characters() {
+ assertThat(sut.setData(Strings.repeat("1", 4001)).getData()).isNotNull();
+ }
+
+ @Test
+ public void text_value_under_4000_characters() {
+ assertThat(sut.setData("text value").getData()).isEqualTo("text value");
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void fail_to_set_out_of_bounds_variation() {
+ sut.setVariation(6, 1d);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void fail_to_get_out_of_bounds_variation() {
+ sut.getVariation(6);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/measure/MeasureFilterDaoTest.java b/sonar-db/src/test/java/org/sonar/db/measure/MeasureFilterDaoTest.java
new file mode 100644
index 00000000000..06efb6a3825
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/measure/MeasureFilterDaoTest.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.measure;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MeasureFilterDaoTest extends AbstractDaoTestCase {
+ private MeasureFilterDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new MeasureFilterDao(getMyBatis());
+ }
+
+ @Test
+ public void should_find_filter() {
+ setupData("shared");
+
+ MeasureFilterDto filter = dao.findSystemFilterByName("Projects");
+
+ assertThat(filter.getId()).isEqualTo(1L);
+ assertThat(filter.getName()).isEqualTo("Projects");
+ }
+
+ @Test
+ public void should_not_find_filter() {
+ setupData("shared");
+
+ assertThat(dao.findSystemFilterByName("Unknown")).isNull();
+ }
+
+ @Test
+ public void should_insert() {
+ setupData("shared");
+
+ MeasureFilterDto filterDto = new MeasureFilterDto();
+ filterDto.setName("Project Treemap");
+ filterDto.setUserId(123L);
+ filterDto.setShared(true);
+ filterDto.setDescription("Treemap of projects");
+ filterDto.setData("qualifiers=TRK|display=treemap");
+
+ dao.insert(filterDto);
+
+ checkTables("shouldInsert", new String[] {"created_at", "updated_at"}, "measure_filters");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/measure/PastMeasureDtoTest.java b/sonar-db/src/test/java/org/sonar/db/measure/PastMeasureDtoTest.java
new file mode 100644
index 00000000000..6b1176088fa
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/measure/PastMeasureDtoTest.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PastMeasureDtoTest {
+
+ @Test
+ public void test_getter_and_setter() throws Exception {
+ PastMeasureDto dto = new PastMeasureDto()
+ .setId(10L)
+ .setValue(1d)
+ .setMetricId(2)
+ .setRuleId(3)
+ .setCharacteristicId(4)
+ .setPersonId(5);
+
+ assertThat(dto.getId()).isEqualTo(10L);
+ assertThat(dto.hasValue()).isTrue();
+ assertThat(dto.getValue()).isEqualTo(1d);
+ assertThat(dto.getMetricId()).isEqualTo(2);
+ assertThat(dto.getRuleId()).isEqualTo(3);
+ assertThat(dto.getCharacteristicId()).isEqualTo(4);
+ assertThat(dto.getPersonId()).isEqualTo(5);
+ }
+
+ @Test
+ public void test_has_value() throws Exception {
+ PastMeasureDto measureWithValue = new PastMeasureDto()
+ .setId(10L)
+ .setValue(1d)
+ .setMetricId(2)
+ .setRuleId(3)
+ .setCharacteristicId(4)
+ .setPersonId(5);
+ assertThat(measureWithValue.hasValue()).isTrue();
+
+ PastMeasureDto measureWithoutValue = new PastMeasureDto()
+ .setId(10L)
+ .setMetricId(2)
+ .setRuleId(3)
+ .setCharacteristicId(4)
+ .setPersonId(5);
+ assertThat(measureWithoutValue.hasValue()).isFalse();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void get_value_throw_a_NPE_if_value_is_null() throws Exception {
+ new PastMeasureDto().getValue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/metric/MetricDtoTest.java b/sonar-db/src/test/java/org/sonar/db/metric/MetricDtoTest.java
new file mode 100644
index 00000000000..47366890df9
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/metric/MetricDtoTest.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.metric;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MetricDtoTest {
+
+ @Test
+ public void getters_and_setters() {
+ MetricDto metricDto = new MetricDto()
+ .setId(1)
+ .setKey("coverage")
+ .setShortName("Coverage")
+ .setDescription("Coverage by unit tests")
+ .setDomain("Tests")
+ .setValueType("PERCENT")
+ .setQualitative(true)
+ .setUserManaged(false)
+ .setWorstValue(0d)
+ .setBestValue(100d)
+ .setOptimizedBestValue(true)
+ .setDirection(1)
+ .setHidden(true)
+ .setDeleteHistoricalData(true)
+ .setEnabled(true);
+
+ assertThat(metricDto.getId()).isEqualTo(1);
+ assertThat(metricDto.getKey()).isEqualTo("coverage");
+ assertThat(metricDto.getShortName()).isEqualTo("Coverage");
+ assertThat(metricDto.getDescription()).isEqualTo("Coverage by unit tests");
+ assertThat(metricDto.getDomain()).isEqualTo("Tests");
+ assertThat(metricDto.getValueType()).isEqualTo("PERCENT");
+ assertThat(metricDto.isQualitative()).isTrue();
+ assertThat(metricDto.isUserManaged()).isFalse();
+ assertThat(metricDto.getWorstValue()).isEqualTo(0d);
+ assertThat(metricDto.getBestValue()).isEqualTo(100d);
+ assertThat(metricDto.isOptimizedBestValue()).isTrue();
+ assertThat(metricDto.getDirection()).isEqualTo(1);
+ assertThat(metricDto.isHidden()).isTrue();
+ assertThat(metricDto.isDeleteHistoricalData()).isTrue();
+ assertThat(metricDto.isEnabled()).isTrue();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/notification/NotificationQueueDaoTest.java b/sonar-db/src/test/java/org/sonar/db/notification/NotificationQueueDaoTest.java
new file mode 100644
index 00000000000..2c2f0ebd65c
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/notification/NotificationQueueDaoTest.java
@@ -0,0 +1,85 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.notification;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NotificationQueueDaoTest extends AbstractDaoTestCase {
+
+ NotificationQueueDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new NotificationQueueDao(getMyBatis());
+ }
+
+ @Test
+ public void should_insert_new_notification_queue() throws Exception {
+ NotificationQueueDto notificationQueueDto = NotificationQueueDto.toNotificationQueueDto(new Notification("email"));
+
+ dao.insert(Arrays.asList(notificationQueueDto));
+
+ assertThat(dao.count()).isEqualTo(1);
+ assertThat(dao.findOldest(1).get(0).toNotification().getType()).isEqualTo("email");
+ }
+
+ @Test
+ public void should_count_notification_queue() {
+ NotificationQueueDto notificationQueueDto = NotificationQueueDto.toNotificationQueueDto(new Notification("email"));
+
+ assertThat(dao.count()).isEqualTo(0);
+
+ dao.insert(Arrays.asList(notificationQueueDto));
+
+ assertThat(dao.count()).isEqualTo(1);
+ }
+
+ @Test
+ public void should_delete_notification() {
+ setupData("should_delete_notification");
+
+ NotificationQueueDto dto1 = new NotificationQueueDto().setId(1L);
+ NotificationQueueDto dto3 = new NotificationQueueDto().setId(3L);
+
+ dao.delete(Arrays.asList(dto1, dto3));
+
+ checkTables("should_delete_notification", "notifications");
+ }
+
+ @Test
+ public void should_findOldest() {
+ setupData("should_findOldest");
+
+ Collection<NotificationQueueDto> result = dao.findOldest(3);
+ assertThat(result).hasSize(3);
+ assertThat(result).extracting("id").containsOnly(1L, 2L, 3L);
+
+ result = dao.findOldest(6);
+ assertThat(result).hasSize(4);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDaoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDaoTest.java
new file mode 100644
index 00000000000..c6f9f03760d
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDaoTest.java
@@ -0,0 +1,137 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupWithPermissionDaoTest extends AbstractDaoTestCase {
+
+ private static final long COMPONENT_ID = 100L;
+
+ private PermissionDao dao;
+
+ @Before
+ public void setUp() {
+ dao = new PermissionDao(getMyBatis());
+ }
+
+ @Test
+ public void select_groups_for_project_permission() {
+ setupData("groups_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<GroupWithPermissionDto> result = dao.selectGroups(query, COMPONENT_ID);
+ assertThat(result).hasSize(4);
+
+ GroupWithPermissionDto anyone = result.get(0);
+ assertThat(anyone.getName()).isEqualTo("Anyone");
+ assertThat(anyone.getDescription()).isNull();
+ assertThat(anyone.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group1 = result.get(1);
+ assertThat(group1.getName()).isEqualTo("sonar-administrators");
+ assertThat(group1.getDescription()).isEqualTo("System administrators");
+ assertThat(group1.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group2 = result.get(2);
+ assertThat(group2.getName()).isEqualTo("sonar-reviewers");
+ assertThat(group2.getDescription()).isEqualTo("Reviewers");
+ assertThat(group2.getPermission()).isNull();
+
+ GroupWithPermissionDto group3 = result.get(3);
+ assertThat(group3.getName()).isEqualTo("sonar-users");
+ assertThat(group3.getDescription()).isEqualTo("Any new users created will automatically join this group");
+ assertThat(group3.getPermission()).isNotNull();
+ }
+
+ @Test
+ public void anyone_group_is_not_returned_when_it_has_no_permission() {
+ setupData("groups_with_permissions");
+
+ // Anyone group has not the permission 'admin', so it's not returned
+ PermissionQuery query = PermissionQuery.builder().permission("admin").build();
+ List<GroupWithPermissionDto> result = dao.selectGroups(query, COMPONENT_ID);
+ assertThat(result).hasSize(3);
+
+ GroupWithPermissionDto group1 = result.get(0);
+ assertThat(group1.getName()).isEqualTo("sonar-administrators");
+ assertThat(group1.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group2 = result.get(1);
+ assertThat(group2.getName()).isEqualTo("sonar-reviewers");
+ assertThat(group2.getPermission()).isNull();
+
+ GroupWithPermissionDto group3 = result.get(2);
+ assertThat(group3.getName()).isEqualTo("sonar-users");
+ assertThat(group3.getPermission()).isNull();
+ }
+
+ @Test
+ public void select_groups_for_global_permission() {
+ setupData("groups_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("admin").build();
+ List<GroupWithPermissionDto> result = dao.selectGroups(query, null);
+ assertThat(result).hasSize(3);
+
+ GroupWithPermissionDto group1 = result.get(0);
+ assertThat(group1.getName()).isEqualTo("sonar-administrators");
+ assertThat(group1.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group2 = result.get(1);
+ assertThat(group2.getName()).isEqualTo("sonar-reviewers");
+ assertThat(group2.getPermission()).isNull();
+
+ GroupWithPermissionDto group3 = result.get(2);
+ assertThat(group3.getName()).isEqualTo("sonar-users");
+ assertThat(group3.getPermission()).isNull();
+ }
+
+ @Test
+ public void search_by_groups_name() {
+ setupData("groups_with_permissions");
+
+ List<GroupWithPermissionDto> result = dao.selectGroups(PermissionQuery.builder().permission("user").search("aDMini").build(), COMPONENT_ID);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("sonar-administrators");
+
+ result = dao.selectGroups(PermissionQuery.builder().permission("user").search("sonar").build(), COMPONENT_ID);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void search_groups_should_be_sorted_by_group_name() {
+ setupData("groups_with_permissions_should_be_sorted_by_group_name");
+
+ List<GroupWithPermissionDto> result = dao.selectGroups(PermissionQuery.builder().permission("user").build(), COMPONENT_ID);
+ assertThat(result).hasSize(4);
+ assertThat(result.get(0).getName()).isEqualTo("Anyone");
+ assertThat(result.get(1).getName()).isEqualTo("sonar-administrators");
+ assertThat(result.get(2).getName()).isEqualTo("sonar-reviewers");
+ assertThat(result.get(3).getName()).isEqualTo("sonar-users");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDtoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDtoTest.java
new file mode 100644
index 00000000000..04101abeb04
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionDtoTest.java
@@ -0,0 +1,54 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import org.junit.Test;
+import org.sonar.core.permission.GroupWithPermission;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupWithPermissionDtoTest {
+
+ @Test
+ public void to_group_with_permission_having_permission() {
+ GroupWithPermission group = new GroupWithPermissionDto()
+ .setName("users")
+ .setDescription("desc")
+ .setPermission("user")
+ .toGroupWithPermission();
+
+ assertThat(group.name()).isEqualTo("users");
+ assertThat(group.description()).isEqualTo("desc");
+ assertThat(group.hasPermission()).isTrue();
+ }
+
+ @Test
+ public void to_group_with_permission_not_having_permission() {
+ GroupWithPermission group = new GroupWithPermissionDto()
+ .setName("users")
+ .setPermission(null)
+ .toGroupWithPermission();
+
+ assertThat(group.name()).isEqualTo("users");
+ assertThat(group.description()).isNull();
+ assertThat(group.hasPermission()).isFalse();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest.java
new file mode 100644
index 00000000000..0cb583bded5
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest.java
@@ -0,0 +1,118 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupWithPermissionTemplateDaoTest extends AbstractDaoTestCase {
+
+ private static final long TEMPLATE_ID = 50L;
+
+ private PermissionTemplateDao dao;
+
+ @Before
+ public void setUp() {
+ dao = new PermissionTemplateDao(getMyBatis(), System2.INSTANCE);
+ }
+
+ @Test
+ public void select_groups() {
+ setupData("groups_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<GroupWithPermissionDto> result = dao.selectGroups(query, TEMPLATE_ID);
+
+ assertThat(result).hasSize(4);
+
+ GroupWithPermissionDto anyone = result.get(0);
+ assertThat(anyone.getName()).isEqualTo("Anyone");
+ assertThat(anyone.getDescription()).isNull();
+ assertThat(anyone.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group1 = result.get(1);
+ assertThat(group1.getName()).isEqualTo("sonar-administrators");
+ assertThat(group1.getDescription()).isEqualTo("System administrators");
+ assertThat(group1.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group2 = result.get(2);
+ assertThat(group2.getName()).isEqualTo("sonar-reviewers");
+ assertThat(group2.getDescription()).isEqualTo("Reviewers");
+ assertThat(group2.getPermission()).isNull();
+
+ GroupWithPermissionDto group3 = result.get(3);
+ assertThat(group3.getName()).isEqualTo("sonar-users");
+ assertThat(group3.getDescription()).isEqualTo("Any new users created will automatically join this group");
+ assertThat(group3.getPermission()).isNotNull();
+ }
+
+ @Test
+ public void anyone_group_is_not_returned_when_it_has_no_permission() {
+ setupData("groups_with_permissions");
+
+ // Anyone group has not the permission 'admin', so it's not returned
+ PermissionQuery query = PermissionQuery.builder().permission("admin").build();
+ List<GroupWithPermissionDto> result = dao.selectGroups(query, TEMPLATE_ID);
+ assertThat(result).hasSize(3);
+
+ GroupWithPermissionDto group1 = result.get(0);
+ assertThat(group1.getName()).isEqualTo("sonar-administrators");
+ assertThat(group1.getPermission()).isNotNull();
+
+ GroupWithPermissionDto group2 = result.get(1);
+ assertThat(group2.getName()).isEqualTo("sonar-reviewers");
+ assertThat(group2.getPermission()).isNull();
+
+ GroupWithPermissionDto group3 = result.get(2);
+ assertThat(group3.getName()).isEqualTo("sonar-users");
+ assertThat(group3.getPermission()).isNull();
+ }
+
+ @Test
+ public void search_by_groups_name() {
+ setupData("groups_with_permissions");
+
+ List<GroupWithPermissionDto> result = dao.selectGroups(PermissionQuery.builder().permission("user").search("aDMini").build(), TEMPLATE_ID);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("sonar-administrators");
+
+ result = dao.selectGroups(PermissionQuery.builder().permission("user").search("sonar").build(), TEMPLATE_ID);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void search_groups_should_be_sorted_by_group_name() {
+ setupData("groups_with_permissions_should_be_sorted_by_group_name");
+
+ List<GroupWithPermissionDto> result = dao.selectGroups(PermissionQuery.builder().permission("user").build(), TEMPLATE_ID);
+ assertThat(result).hasSize(4);
+ assertThat(result.get(0).getName()).isEqualTo("Anyone");
+ assertThat(result.get(1).getName()).isEqualTo("sonar-administrators");
+ assertThat(result.get(2).getName()).isEqualTo("sonar-reviewers");
+ assertThat(result.get(3).getName()).isEqualTo("sonar-users");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTest.java b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTest.java
new file mode 100644
index 00000000000..7ddc78d1308
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/GroupWithPermissionTest.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.permission;
+
+import org.junit.Test;
+import org.sonar.core.permission.GroupWithPermission;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupWithPermissionTest {
+
+ @Test
+ public void test_setters_and_getters() throws Exception {
+ GroupWithPermission user = new GroupWithPermission()
+ .setName("users")
+ .hasPermission(true);
+
+ assertThat(user.name()).isEqualTo("users");
+ assertThat(user.hasPermission()).isTrue();
+ }
+
+ @Test
+ public void test_equals() throws Exception {
+ assertThat(new GroupWithPermission().setName("users")).isEqualTo(new GroupWithPermission().setName("users"));
+ assertThat(new GroupWithPermission().setName("users")).isNotEqualTo(new GroupWithPermission().setName("reviewers"));
+
+ GroupWithPermission group = new GroupWithPermission()
+ .setName("users")
+ .hasPermission(true);
+ assertThat(group).isEqualTo(group);
+ }
+
+ @Test
+ public void test_hashcode() throws Exception {
+ assertThat(new GroupWithPermission().setName("users").hashCode()).isEqualTo(new GroupWithPermission().setName("users").hashCode());
+ assertThat(new GroupWithPermission().setName("users").hashCode()).isNotEqualTo(new GroupWithPermission().setName("reviewers").hashCode());
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/PermissionFacadeTest.java b/sonar-db/src/test/java/org/sonar/db/permission/PermissionFacadeTest.java
new file mode 100644
index 00000000000..1a965020c71
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/PermissionFacadeTest.java
@@ -0,0 +1,218 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.user.RoleDao;
+import org.sonar.db.user.UserDao;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PermissionFacadeTest extends AbstractDaoTestCase {
+
+ @Rule
+ public ExpectedException throwable = ExpectedException.none();
+
+ DbSession session;
+ System2 system2;
+ PermissionFacade permissionFacade;
+ PermissionTemplateDao permissionTemplateDao;
+ ResourceDao resourceDao;
+
+ @Before
+ public void setUp() {
+ system2 = mock(System2.class);
+ when(system2.now()).thenReturn(123456789L);
+
+ session = getMyBatis().openSession(false);
+ RoleDao roleDao = new RoleDao();
+ UserDao userDao = new UserDao(getMyBatis(), system2);
+ permissionTemplateDao = new PermissionTemplateDao(getMyBatis(), System2.INSTANCE);
+ Settings settings = new Settings();
+ resourceDao = new ResourceDao(getMyBatis(), system2);
+ permissionFacade = new PermissionFacade(roleDao, userDao, resourceDao, permissionTemplateDao, settings);
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void should_apply_permission_template() {
+ setupData("should_apply_permission_template");
+
+ assertThat(permissionFacade.selectGroupPermissions(session, "sonar-administrators", 123L)).isEmpty();
+ assertThat(permissionFacade.selectGroupPermissions(session, "sonar-users", 123L)).isEmpty();
+ assertThat(permissionFacade.selectGroupPermissions(session, "Anyone", 123L)).isEmpty();
+ assertThat(permissionFacade.selectUserPermissions(session, "marius", 123L)).isEmpty();
+
+ permissionFacade.applyPermissionTemplate(session, "default_20130101_010203", 123L);
+
+ assertThat(permissionFacade.selectGroupPermissions(session, "sonar-administrators", 123L)).containsOnly("admin", "issueadmin");
+ assertThat(permissionFacade.selectGroupPermissions(session, "sonar-users", 123L)).containsOnly("user", "codeviewer");
+ assertThat(permissionFacade.selectGroupPermissions(session, "Anyone", 123L)).containsOnly("user", "codeviewer");
+
+ assertThat(permissionFacade.selectUserPermissions(session, "marius", 123L)).containsOnly("admin");
+
+ assertThat(resourceDao.getResource(123L, session).getAuthorizationUpdatedAt()).isEqualTo(123456789L);
+ }
+
+ @Test
+ public void should_count_component_permissions() {
+ setupData("should_count_component_permissions");
+
+ assertThat(permissionFacade.countComponentPermissions(session, 123L)).isEqualTo(2);
+ }
+
+ @Test
+ public void should_add_user_permission() {
+ setupData("should_add_user_permission");
+
+ permissionFacade.insertUserPermission(123L, 200L, UserRole.ADMIN, session);
+ session.commit();
+
+ checkTable("should_add_user_permission", "user_roles", "user_id", "resource_id", "role");
+ checkTable("should_add_user_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_delete_user_permission() {
+ setupData("should_delete_user_permission");
+
+ permissionFacade.deleteUserPermission(123L, 200L, UserRole.ADMIN, session);
+ session.commit();
+
+ checkTable("should_delete_user_permission", "user_roles", "user_id", "resource_id", "role");
+ checkTable("should_delete_user_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_insert_group_permission() {
+ setupData("should_insert_group_permission");
+
+ permissionFacade.insertGroupPermission(123L, 100L, UserRole.USER, session);
+ session.commit();
+
+ checkTable("should_insert_group_permission", "group_roles", "group_id", "resource_id", "role");
+ checkTable("should_insert_group_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_insert_group_name_permission() {
+ setupData("should_insert_group_permission");
+
+ permissionFacade.insertGroupPermission(123L, "devs", UserRole.USER, session);
+ session.commit();
+
+ checkTable("should_insert_group_permission", "group_roles", "group_id", "resource_id", "role");
+ checkTable("should_insert_group_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_insert_anyone_group_permission() {
+ setupData("should_insert_anyone_group_permission");
+
+ permissionFacade.insertGroupPermission(123L, "Anyone", UserRole.USER, session);
+ session.commit();
+
+ checkTable("should_insert_anyone_group_permission", "group_roles", "group_id", "resource_id", "role");
+ checkTable("should_insert_anyone_group_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_delete_group_permission() {
+ setupData("should_delete_group_permission");
+
+ permissionFacade.deleteGroupPermission(123L, 100L, UserRole.USER, session);
+ session.commit();
+
+ checkTable("should_delete_group_permission", "group_roles", "group_id", "resource_id", "role");
+ checkTable("should_delete_group_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_delete_group_name_permission() {
+ setupData("should_delete_group_permission");
+
+ permissionFacade.deleteGroupPermission(123L, "devs", UserRole.USER, session);
+ session.commit();
+
+ checkTable("should_delete_group_permission", "group_roles", "group_id", "resource_id", "role");
+ checkTable("should_delete_group_permission", "projects", "authorization_updated_at");
+ }
+
+ @Test
+ public void should_retrieve_permission_template() {
+ PermissionTemplateDto permissionTemplateDto = new PermissionTemplateDto().setName("Test template").setKee("test_template");
+ PermissionTemplateDto templateWithPermissions = new PermissionTemplateDto().setKee("test_template");
+ permissionTemplateDao = mock(PermissionTemplateDao.class);
+ when(permissionTemplateDao.selectTemplateByKey(session, "test_template")).thenReturn(permissionTemplateDto);
+ when(permissionTemplateDao.selectPermissionTemplate(session, "test_template")).thenReturn(templateWithPermissions);
+
+ permissionFacade = new PermissionFacade(null, null, null, permissionTemplateDao, null);
+
+ PermissionTemplateDto permissionTemplate = permissionFacade.getPermissionTemplateWithPermissions(session, "test_template");
+
+ assertThat(permissionTemplate).isSameAs(templateWithPermissions);
+ }
+
+ @Test
+ public void should_fail_on_unmatched_template() {
+ throwable.expect(IllegalArgumentException.class);
+
+ permissionTemplateDao = mock(PermissionTemplateDao.class);
+
+ permissionFacade = new PermissionFacade(null, null, null, permissionTemplateDao, null);
+ permissionFacade.getPermissionTemplateWithPermissions(session, "unmatched");
+ }
+
+ @Test
+ public void should_remove_all_permissions() {
+ setupData("should_remove_all_permissions");
+
+ assertThat(permissionFacade.selectGroupPermissions(session, "devs", 123L)).hasSize(1);
+ assertThat(permissionFacade.selectGroupPermissions(session, "other", 123L)).isEmpty();
+ assertThat(permissionFacade.selectUserPermissions(session, "dave.loper", 123L)).hasSize(1);
+ assertThat(permissionFacade.selectUserPermissions(session, "other.user", 123L)).isEmpty();
+
+ permissionFacade.removeAllPermissions(123L, session);
+ session.commit();
+
+ checkTable("should_remove_all_permissions", "group_roles", "group_id", "resource_id", "role");
+ checkTable("should_remove_all_permissions", "user_roles", "user_id", "resource_id", "role");
+
+ assertThat(permissionFacade.selectGroupPermissions(session, "devs", 123L)).isEmpty();
+ assertThat(permissionFacade.selectUserPermissions(session, "dave.loper", 123L)).isEmpty();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java
new file mode 100644
index 00000000000..14902f25566
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java
@@ -0,0 +1,244 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class PermissionTemplateDaoTest extends AbstractDaoTestCase {
+
+ Date now;
+ PermissionTemplateDao permissionTemplateDao;
+ DbSession session;
+ System2 system = mock(System2.class);
+
+ @Before
+ public void setUpDao() throws ParseException {
+ session = getMyBatis().openSession(false);
+ now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2013-01-02 01:04:05");
+ when(system.now()).thenReturn(now.getTime());
+ permissionTemplateDao = new PermissionTemplateDao(getMyBatis(), system);
+ }
+
+ @After
+ public void after() {
+ this.session.close();
+ }
+
+ @Test
+ public void should_create_permission_template() {
+ setupData("createPermissionTemplate");
+ PermissionTemplateDto permissionTemplate = permissionTemplateDao.createPermissionTemplate("my template", "my description", "myregexp");
+ assertThat(permissionTemplate).isNotNull();
+ assertThat(permissionTemplate.getId()).isEqualTo(1L);
+ checkTable("createPermissionTemplate", "permission_templates", "id", "name", "kee", "description");
+ }
+
+ @Test
+ public void should_normalize_kee_on_template_creation() {
+ setupData("createNonAsciiPermissionTemplate");
+ PermissionTemplateDto permissionTemplate = permissionTemplateDao.createPermissionTemplate("Môü Gnô Gnèçàß", "my description", null);
+ assertThat(permissionTemplate).isNotNull();
+ assertThat(permissionTemplate.getId()).isEqualTo(1L);
+ checkTable("createNonAsciiPermissionTemplate", "permission_templates", "id", "name", "kee", "description");
+ }
+
+ @Test
+ public void should_skip_key_normalization_on_default_template() {
+
+ PermissionTemplateMapper mapper = mock(PermissionTemplateMapper.class);
+
+ DbSession session = mock(DbSession.class);
+ when(session.getMapper(PermissionTemplateMapper.class)).thenReturn(mapper);
+
+ MyBatis myBatis = mock(MyBatis.class);
+ when(myBatis.openSession(false)).thenReturn(session);
+
+ permissionTemplateDao = new PermissionTemplateDao(myBatis, system);
+ PermissionTemplateDto permissionTemplate = permissionTemplateDao.createPermissionTemplate(PermissionTemplateDto.DEFAULT.getName(), null, null);
+
+ verify(mapper).insert(permissionTemplate);
+ verify(session).commit();
+
+ assertThat(permissionTemplate.getKee()).isEqualTo(PermissionTemplateDto.DEFAULT.getKee());
+ }
+
+ @Test
+ public void should_select_permission_template() {
+ setupData("selectPermissionTemplate");
+ PermissionTemplateDto permissionTemplate = permissionTemplateDao.selectPermissionTemplate("my_template_20130102_030405");
+
+ assertThat(permissionTemplate).isNotNull();
+ assertThat(permissionTemplate.getName()).isEqualTo("my template");
+ assertThat(permissionTemplate.getKee()).isEqualTo("my_template_20130102_030405");
+ assertThat(permissionTemplate.getDescription()).isEqualTo("my description");
+ assertThat(permissionTemplate.getUsersPermissions()).hasSize(3);
+ assertThat(permissionTemplate.getUsersPermissions()).extracting("userId").containsOnly(1L, 2L, 1L);
+ assertThat(permissionTemplate.getUsersPermissions()).extracting("userLogin").containsOnly("login1", "login2", "login2");
+ assertThat(permissionTemplate.getUsersPermissions()).extracting("userName").containsOnly("user1", "user2", "user2");
+ assertThat(permissionTemplate.getUsersPermissions()).extracting("permission").containsOnly("user_permission1", "user_permission1", "user_permission2");
+ assertThat(permissionTemplate.getGroupsPermissions()).hasSize(3);
+ assertThat(permissionTemplate.getGroupsPermissions()).extracting("groupId").containsOnly(1L, 2L, null);
+ assertThat(permissionTemplate.getGroupsPermissions()).extracting("groupName").containsOnly("group1", "group2", null);
+ assertThat(permissionTemplate.getGroupsPermissions()).extracting("permission").containsOnly("group_permission1", "group_permission1", "group_permission2");
+ }
+
+ @Test
+ public void should_select_empty_permission_template() {
+ setupData("selectEmptyPermissionTemplate");
+ PermissionTemplateDto permissionTemplate = permissionTemplateDao.selectPermissionTemplate("my_template_20130102_030405");
+
+ assertThat(permissionTemplate).isNotNull();
+ assertThat(permissionTemplate.getName()).isEqualTo("my template");
+ assertThat(permissionTemplate.getDescription()).isEqualTo("my description");
+ assertThat(permissionTemplate.getUsersPermissions()).isNull();
+ assertThat(permissionTemplate.getGroupsPermissions()).isNull();
+ }
+
+ @Test
+ public void should_select_permission_template_by_key() {
+ setupData("selectPermissionTemplate");
+
+ PermissionTemplateDto permissionTemplate = permissionTemplateDao.selectTemplateByKey("my_template_20130102_030405");
+
+ assertThat(permissionTemplate).isNotNull();
+ assertThat(permissionTemplate.getId()).isEqualTo(1L);
+ assertThat(permissionTemplate.getName()).isEqualTo("my template");
+ assertThat(permissionTemplate.getKee()).isEqualTo("my_template_20130102_030405");
+ assertThat(permissionTemplate.getDescription()).isEqualTo("my description");
+ }
+
+ @Test
+ public void should_select_all_permission_templates() {
+ setupData("selectAllPermissionTemplates");
+
+ List<PermissionTemplateDto> permissionTemplates = permissionTemplateDao.selectAllPermissionTemplates();
+
+ assertThat(permissionTemplates).hasSize(3);
+ assertThat(permissionTemplates).extracting("id").containsOnly(1L, 2L, 3L);
+ assertThat(permissionTemplates).extracting("name").containsOnly("template1", "template2", "template3");
+ assertThat(permissionTemplates).extracting("kee").containsOnly("template1_20130102_030405", "template2_20130102_030405", "template3_20130102_030405");
+ assertThat(permissionTemplates).extracting("description").containsOnly("description1", "description2", "description3");
+ }
+
+ @Test
+ public void should_update_permission_template() {
+ setupData("updatePermissionTemplate");
+
+ permissionTemplateDao.updatePermissionTemplate(1L, "new_name", "new_description", "new_regexp");
+
+ checkTable("updatePermissionTemplate", "permission_templates", "id", "name", "kee", "description");
+ }
+
+ @Test
+ public void should_delete_permission_template() {
+ setupData("deletePermissionTemplate");
+
+ permissionTemplateDao.deletePermissionTemplate(1L);
+
+ checkTable("deletePermissionTemplate", "permission_templates", "id", "name", "description");
+ checkTable("deletePermissionTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("deletePermissionTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void should_add_user_permission_to_template() {
+ setupData("addUserPermissionToTemplate");
+ permissionTemplateDao.addUserPermission(1L, 1L, "new_permission");
+
+ checkTable("addUserPermissionToTemplate", "permission_templates", "id", "name", "description");
+ checkTable("addUserPermissionToTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("addUserPermissionToTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void should_remove_user_permission_from_template() {
+ setupData("removeUserPermissionFromTemplate");
+ permissionTemplateDao.removeUserPermission(1L, 2L, "permission_to_remove");
+
+ checkTable("removeUserPermissionFromTemplate", "permission_templates", "id", "name", "description");
+ checkTable("removeUserPermissionFromTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("removeUserPermissionFromTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void should_add_group_permission_to_template() {
+ setupData("addGroupPermissionToTemplate");
+ permissionTemplateDao.addGroupPermission(1L, 1L, "new_permission");
+
+ checkTable("addGroupPermissionToTemplate", "permission_templates", "id", "name", "description");
+ checkTable("addGroupPermissionToTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("addGroupPermissionToTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void should_remove_group_permission_from_template() {
+ setupData("removeGroupPermissionFromTemplate");
+ permissionTemplateDao.removeGroupPermission(1L, 2L, "permission_to_remove");
+
+ checkTable("removeGroupPermissionFromTemplate", "permission_templates", "id", "name", "description");
+ checkTable("removeGroupPermissionFromTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("removeGroupPermissionFromTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void remove_by_group() {
+ setupData("remove_by_group");
+ permissionTemplateDao.removeByGroup(2L, session);
+ session.commit();
+
+ checkTable("remove_by_group", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void should_add_group_permission_with_null_name() {
+ setupData("addNullGroupPermissionToTemplate");
+ permissionTemplateDao.addGroupPermission(1L, null, "new_permission");
+
+ checkTable("addNullGroupPermissionToTemplate", "permission_templates", "id", "name", "description");
+ checkTable("addNullGroupPermissionToTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("addNullGroupPermissionToTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+
+ @Test
+ public void should_remove_group_permission_with_null_name() {
+ setupData("removeNullGroupPermissionFromTemplate");
+ permissionTemplateDao.removeGroupPermission(1L, null, "permission_to_remove");
+
+ checkTable("removeNullGroupPermissionFromTemplate", "permission_templates", "id", "name", "description");
+ checkTable("removeNullGroupPermissionFromTemplate", "perm_templates_users", "id", "template_id", "user_id", "permission_reference");
+ checkTable("removeNullGroupPermissionFromTemplate", "perm_templates_groups", "id", "template_id", "group_id", "permission_reference");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDaoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDaoTest.java
new file mode 100644
index 00000000000..7533cb88c83
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDaoTest.java
@@ -0,0 +1,164 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserWithPermissionDaoTest extends AbstractDaoTestCase {
+
+ private static final long COMPONENT_ID = 100L;
+
+ private PermissionDao dao;
+
+ @Before
+ public void setUp() {
+ dao = new PermissionDao(getMyBatis());
+ }
+
+ @Test
+ public void select_all_users_for_project_permission() {
+ setupData("users_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<UserWithPermissionDto> result = dao.selectUsers(query, COMPONENT_ID);
+ assertThat(result).hasSize(3);
+
+ UserWithPermissionDto user1 = result.get(0);
+ assertThat(user1.getLogin()).isEqualTo("user1");
+ assertThat(user1.getName()).isEqualTo("User1");
+ assertThat(user1.getPermission()).isNotNull();
+
+ UserWithPermissionDto user2 = result.get(1);
+ assertThat(user2.getLogin()).isEqualTo("user2");
+ assertThat(user2.getName()).isEqualTo("User2");
+ assertThat(user2.getPermission()).isNotNull();
+
+ UserWithPermissionDto user3 = result.get(2);
+ assertThat(user3.getLogin()).isEqualTo("user3");
+ assertThat(user3.getName()).isEqualTo("User3");
+ assertThat(user3.getPermission()).isNull();
+ }
+
+ @Test
+ public void select_all_users_for_global_permission() {
+ setupData("users_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("admin").build();
+ List<UserWithPermissionDto> result = dao.selectUsers(query, null);
+ assertThat(result).hasSize(3);
+
+ UserWithPermissionDto user1 = result.get(0);
+ assertThat(user1.getName()).isEqualTo("User1");
+ assertThat(user1.getPermission()).isNotNull();
+
+ UserWithPermissionDto user2 = result.get(1);
+ assertThat(user2.getName()).isEqualTo("User2");
+ assertThat(user2.getPermission()).isNull();
+
+ UserWithPermissionDto user3 = result.get(2);
+ assertThat(user3.getName()).isEqualTo("User3");
+ assertThat(user3.getPermission()).isNull();
+ }
+
+ @Test
+ public void select_only_user_with_permission() {
+ setupData("users_with_permissions");
+
+ // user1 and user2 have permission user
+ assertThat(dao.selectUsers(PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build(), COMPONENT_ID)).hasSize(2);
+ }
+
+ @Test
+ public void select_only_user_without_permission() {
+ setupData("users_with_permissions");
+
+ // Only user3 has not the user permission
+ assertThat(dao.selectUsers(PermissionQuery.builder().permission("user").membership(PermissionQuery.OUT).build(), COMPONENT_ID)).hasSize(1);
+ }
+
+ @Test
+ public void search_by_user_name() {
+ setupData("users_with_permissions");
+
+ List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").search("SEr1").build(), COMPONENT_ID);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("User1");
+
+ result = dao.selectUsers(PermissionQuery.builder().permission("user").search("user").build(), COMPONENT_ID);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void select_only_enable_users() {
+ setupData("select_only_enable_users");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<UserWithPermissionDto> result = dao.selectUsers(query, COMPONENT_ID);
+ assertThat(result).hasSize(3);
+
+ // Disabled user should not be returned
+ assertThat(Iterables.find(result, new Predicate<UserWithPermissionDto>() {
+ @Override
+ public boolean apply(@Nullable UserWithPermissionDto input) {
+ return input.getLogin().equals("disabledUser");
+ }
+ }, null)).isNull();
+ }
+
+ @Test
+ public void should_be_sorted_by_user_name() {
+ setupData("users_with_permissions_should_be_sorted_by_user_name");
+
+ List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID);
+ assertThat(result).hasSize(3);
+ assertThat(result.get(0).getName()).isEqualTo("User1");
+ assertThat(result.get(1).getName()).isEqualTo("User2");
+ assertThat(result.get(2).getName()).isEqualTo("User3");
+ }
+
+ @Test
+ public void should_be_paginated() {
+ setupData("users_with_permissions");
+
+ List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 0, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("User1");
+ assertThat(result.get(1).getName()).isEqualTo("User2");
+
+ result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 1, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("User2");
+ assertThat(result.get(1).getName()).isEqualTo("User3");
+
+ result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 2, 1);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("User3");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDtoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDtoTest.java
new file mode 100644
index 00000000000..56d951ebb2d
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDtoTest.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import org.junit.Test;
+import org.sonar.core.permission.UserWithPermission;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserWithPermissionDtoTest {
+
+ @Test
+ public void to_user_with_permission_having_permission() {
+ UserWithPermission user = new UserWithPermissionDto()
+ .setName("Arthur")
+ .setLogin("arthur")
+ .setPermission("user")
+ .toUserWithPermission();
+
+ assertThat(user.name()).isEqualTo("Arthur");
+ assertThat(user.login()).isEqualTo("arthur");
+ assertThat(user.hasPermission()).isTrue();
+ }
+
+ @Test
+ public void to_user_with_permission_not_having_permission() {
+ UserWithPermission user = new UserWithPermissionDto()
+ .setName("Arthur")
+ .setLogin("arthur")
+ .setPermission(null)
+ .toUserWithPermission();
+
+ assertThat(user.name()).isEqualTo("Arthur");
+ assertThat(user.login()).isEqualTo("arthur");
+ assertThat(user.hasPermission()).isFalse();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTemplateDaoTest.java b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTemplateDaoTest.java
new file mode 100644
index 00000000000..d142ce71cbf
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTemplateDaoTest.java
@@ -0,0 +1,162 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.permission;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserWithPermissionTemplateDaoTest extends AbstractDaoTestCase {
+
+ private static final Long TEMPLATE_ID = 50L;
+
+ private PermissionTemplateDao dao;
+
+ @Before
+ public void setUp() {
+ dao = new PermissionTemplateDao(getMyBatis(), System2.INSTANCE);
+ }
+
+ @Test
+ public void select_all_users() {
+ setupData("users_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<UserWithPermissionDto> result = dao.selectUsers(query, TEMPLATE_ID);
+ assertThat(result).hasSize(3);
+
+ UserWithPermissionDto user1 = result.get(0);
+ assertThat(user1.getLogin()).isEqualTo("user1");
+ assertThat(user1.getName()).isEqualTo("User1");
+ assertThat(user1.getPermission()).isNotNull();
+
+ UserWithPermissionDto user2 = result.get(1);
+ assertThat(user2.getLogin()).isEqualTo("user2");
+ assertThat(user2.getName()).isEqualTo("User2");
+ assertThat(user2.getPermission()).isNotNull();
+
+ UserWithPermissionDto user3 = result.get(2);
+ assertThat(user3.getLogin()).isEqualTo("user3");
+ assertThat(user3.getName()).isEqualTo("User3");
+ assertThat(user3.getPermission()).isNull();
+ }
+
+ @Test
+ public void return_nothing_on_unknown_template_key() {
+ setupData("users_with_permissions");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<UserWithPermissionDto> result = dao.selectUsers(query, 999L);
+ assertThat(result).hasSize(3);
+
+ UserWithPermissionDto user1 = result.get(0);
+ assertThat(user1.getPermission()).isNull();
+
+ UserWithPermissionDto user2 = result.get(1);
+ assertThat(user2.getPermission()).isNull();
+
+ UserWithPermissionDto user3 = result.get(2);
+ assertThat(user3.getPermission()).isNull();
+ }
+
+ @Test
+ public void select_only_user_with_permission() {
+ setupData("users_with_permissions");
+
+ // user1 and user2 have permission user
+ assertThat(dao.selectUsers(PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build(), TEMPLATE_ID)).hasSize(2);
+ }
+
+ @Test
+ public void select_only_user_without_permission() {
+ setupData("users_with_permissions");
+
+ // Only user3 has not the user permission
+ assertThat(dao.selectUsers(PermissionQuery.builder().permission("user").membership(PermissionQuery.OUT).build(), TEMPLATE_ID)).hasSize(1);
+ }
+
+ @Test
+ public void select_only_enable_users() {
+ setupData("select_only_enable_users");
+
+ PermissionQuery query = PermissionQuery.builder().permission("user").build();
+ List<UserWithPermissionDto> result = dao.selectUsers(query, 999L);
+ assertThat(result).hasSize(3);
+
+ // Disabled user should not be returned
+ assertThat(Iterables.find(result, new Predicate<UserWithPermissionDto>() {
+ @Override
+ public boolean apply(@Nullable UserWithPermissionDto input) {
+ return input.getLogin().equals("disabledUser");
+ }
+ }, null)).isNull();
+ }
+
+ @Test
+ public void search_by_user_name() {
+ setupData("users_with_permissions");
+
+ List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").search("SEr1").build(), TEMPLATE_ID);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("User1");
+
+ result = dao.selectUsers(PermissionQuery.builder().permission("user").search("user").build(), TEMPLATE_ID);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void should_be_sorted_by_user_name() {
+ setupData("users_with_permissions_should_be_sorted_by_user_name");
+
+ List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), TEMPLATE_ID);
+ assertThat(result).hasSize(3);
+ assertThat(result.get(0).getName()).isEqualTo("User1");
+ assertThat(result.get(1).getName()).isEqualTo("User2");
+ assertThat(result.get(2).getName()).isEqualTo("User3");
+ }
+
+ @Test
+ public void should_be_paginated() {
+ setupData("users_with_permissions");
+
+ List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), TEMPLATE_ID, 0, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("User1");
+ assertThat(result.get(1).getName()).isEqualTo("User2");
+
+ result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), TEMPLATE_ID, 1, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("User2");
+ assertThat(result.get(1).getName()).isEqualTo("User3");
+
+ result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), TEMPLATE_ID, 2, 1);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("User3");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTest.java b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTest.java
new file mode 100644
index 00000000000..baada745f8c
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionTest.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.permission;
+
+import org.junit.Test;
+import org.sonar.core.permission.UserWithPermission;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserWithPermissionTest {
+
+ @Test
+ public void test_setters_and_getters() throws Exception {
+ UserWithPermission user = new UserWithPermission()
+ .setName("Arthur")
+ .setLogin("arthur")
+ .hasPermission(true);
+
+ assertThat(user.name()).isEqualTo("Arthur");
+ assertThat(user.login()).isEqualTo("arthur");
+ assertThat(user.hasPermission()).isTrue();
+ }
+
+ @Test
+ public void test_equals() throws Exception {
+ assertThat(new UserWithPermission().setLogin("arthur")).isEqualTo(new UserWithPermission().setLogin("arthur"));
+ assertThat(new UserWithPermission().setLogin("arthur")).isNotEqualTo(new UserWithPermission().setLogin("john"));
+
+ UserWithPermission user = new UserWithPermission()
+ .setName("Arthur")
+ .setLogin("arthur")
+ .hasPermission(true);
+ assertThat(user).isEqualTo(user);
+ }
+
+ @Test
+ public void test_hashcode() throws Exception {
+ assertThat(new UserWithPermission().setLogin("arthur").hashCode()).isEqualTo(new UserWithPermission().setLogin("arthur").hashCode());
+ assertThat(new UserWithPermission().setLogin("arthur").hashCode()).isNotEqualTo(new UserWithPermission().setLogin("john").hashCode());
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java b/sonar-db/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java
new file mode 100644
index 00000000000..dc2110c938e
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.profiling;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.junit.Test;
+import org.sonar.test.TestUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class InvocationUtilsTest {
+
+ @Test
+ public void should_return_result() throws Throwable {
+ String toString = "toString";
+ Object target = mock(Object.class);
+ when(target.toString()).thenReturn(toString);
+
+ assertThat(InvocationUtils.invokeQuietly(target, Object.class.getMethod("toString"), new Object[0])).isEqualTo(toString);
+ }
+
+ @Test
+ public void should_throw_declared_exception() throws Throwable {
+ Connection target = mock(Connection.class);
+ String failSql = "any sql";
+ when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected"));
+
+ try {
+ InvocationUtils.invokeQuietly(target, Connection.class.getMethod("prepareStatement", String.class), new Object[] {failSql});
+ fail();
+ } catch (Throwable t) {
+ assertThat(t).isInstanceOf(SQLException.class);
+ }
+ }
+
+ @Test
+ public void should_wrap_undeclared_exception() throws Throwable {
+ Connection target = mock(Connection.class);
+ String failSql = "any sql";
+ when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected"));
+
+ try {
+ InvocationUtils.invokeQuietly(target, Object.class.getMethod("wait"), new Object[0]);
+ fail();
+ } catch (Throwable t) {
+ assertThat(t).isInstanceOf(IllegalStateException.class);
+ }
+ }
+
+ @Test
+ public void only_static_methods() {
+ assertThat(TestUtils.hasOnlyPrivateConstructors(InvocationUtils.class)).isTrue();
+
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java b/sonar-db/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java
new file mode 100644
index 00000000000..d52e95531f0
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.profiling;
+
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.log.LogTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ProfiledDataSourceTest {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Test
+ public void log_sql_requests() throws Exception {
+ BasicDataSource originDataSource = mock(BasicDataSource.class);
+
+ Connection connection = mock(Connection.class);
+ when(originDataSource.getConnection()).thenReturn(connection);
+
+ String sql = "select 'polop' from dual;";
+ String sqlWithParams = "insert into polop (col1, col2, col3, col4) values (?, ?, ?, ?, ?);";
+ int param1 = 42;
+ String param2 = "plouf";
+ Date param3 = new Date(System.currentTimeMillis());
+ Timestamp param4 = new Timestamp(System.currentTimeMillis());
+ byte[] param5 = "blob".getBytes("UTF-8");
+
+ PreparedStatement preparedStatement = mock(PreparedStatement.class);
+ when(connection.prepareStatement(sqlWithParams)).thenReturn(preparedStatement);
+ when(preparedStatement.execute()).thenReturn(true);
+
+ Statement statement = mock(Statement.class);
+ when(connection.createStatement()).thenReturn(statement);
+ when(statement.execute(sql)).thenReturn(true);
+
+ ProfiledDataSource ds = new ProfiledDataSource(originDataSource);
+
+ assertThat(ds.getUrl()).isNull();
+ assertThat(ds.getConnection().getClientInfo()).isNull();
+ PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams);
+ preparedStatementProxy.setInt(1, param1);
+ preparedStatementProxy.setString(2, param2);
+ preparedStatementProxy.setDate(3, param3);
+ preparedStatementProxy.setTimestamp(4, param4);
+ preparedStatementProxy.setBlob(5, new ByteArrayInputStream(param5));
+ assertThat(preparedStatementProxy.getConnection()).isNull();
+ assertThat(preparedStatementProxy.execute()).isTrue();
+ final Statement statementProxy = ds.getConnection().createStatement();
+ assertThat(statementProxy.getConnection()).isNull();
+ assertThat(statementProxy.execute(sql)).isTrue();
+
+ assertThat(logTester.logs()).hasSize(2);
+ assertThat(logTester.logs().get(1)).contains(sql);
+ }
+
+ @Test
+ public void delegate_to_underlying_datasource() throws Exception {
+ BasicDataSource delegate = mock(BasicDataSource.class);
+ ProfiledDataSource proxy = new ProfiledDataSource(delegate);
+
+ // painful to call all methods
+ // so using reflection to check that calls does not fail
+ // Limitation: methods with parameters are not tested and calls to
+ // underlying datasource are not verified
+ for (Method method : ProfiledDataSource.class.getDeclaredMethods()) {
+ if (method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers())) {
+ method.invoke(proxy);
+ }
+ }
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java b/sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
new file mode 100644
index 00000000000..3ad2527f610
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
@@ -0,0 +1,340 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.property;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class PropertiesDaoTest extends AbstractDaoTestCase {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ DbSession session;
+ PropertiesDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new PropertiesDao(getMyBatis());
+ session = getMyBatis().openSession(false);
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void shouldFindUsersForNotification() {
+ setupData("shouldFindUsersForNotification");
+
+ List<String> users = dao.findUsersForNotification("NewViolations", "Email", null);
+ assertThat(users).isEmpty();
+
+ users = dao.findUsersForNotification("NewViolations", "Email", "uuid_78");
+ assertThat(users).isEmpty();
+
+ users = dao.findUsersForNotification("NewViolations", "Email", "uuid_45");
+ assertThat(users).hasSize(1);
+ assertThat(users).containsOnly("user2");
+
+ users = dao.findUsersForNotification("NewViolations", "Twitter", null);
+ assertThat(users).hasSize(1);
+ assertThat(users).containsOnly("user3");
+
+ users = dao.findUsersForNotification("NewViolations", "Twitter", "uuid_78");
+ assertThat(users).isEmpty();
+
+ users = dao.findUsersForNotification("NewViolations", "Twitter", "uuid_56");
+ assertThat(users).hasSize(2);
+ assertThat(users).containsOnly("user1", "user3");
+ }
+
+ @Test
+ public void findNotificationSubscribers() {
+ setupData("findNotificationSubscribers");
+
+ // Nobody is subscribed
+ List<String> users = dao.findNotificationSubscribers("NotSexyDispatcher", "Email", "org.apache:struts");
+ assertThat(users).isEmpty();
+
+ // Global subscribers
+ users = dao.findNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", "org.apache:struts");
+ assertThat(users).containsOnly("simon");
+
+ users = dao.findNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", null);
+ assertThat(users).containsOnly("simon");
+
+ // Project subscribers
+ users = dao.findNotificationSubscribers("DispatcherWithProjectSubscribers", "Email", "org.apache:struts");
+ assertThat(users).containsOnly("eric");
+
+ // Global + Project subscribers
+ users = dao.findNotificationSubscribers("DispatcherWithGlobalAndProjectSubscribers", "Email", "org.apache:struts");
+ assertThat(users).containsOnly("eric", "simon");
+ }
+
+ @Test
+ public void hasNotificationSubscribers() {
+ setupData("findNotificationSubscribers");
+
+ // Nobody is subscribed
+ assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("NotSexyDispatcher"))).isFalse();
+
+ // Global subscribers
+ assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("DispatcherWithGlobalSubscribers"))).isTrue();
+
+ // Project subscribers
+ assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("DispatcherWithProjectSubscribers"))).isTrue();
+ assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", Arrays.asList("DispatcherWithProjectSubscribers"))).isFalse();
+
+ // Global + Project subscribers
+ assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
+ assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", Arrays.asList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
+ }
+
+ @Test
+ public void selectGlobalProperties() {
+ setupData("selectGlobalProperties");
+ List<PropertyDto> properties = dao.selectGlobalProperties();
+ assertThat(properties.size(), is(2));
+
+ PropertyDto first = findById(properties, 1);
+ assertThat(first.getKey(), is("global.one"));
+ assertThat(first.getValue(), is("one"));
+
+ PropertyDto second = findById(properties, 2);
+ assertThat(second.getKey(), is("global.two"));
+ assertThat(second.getValue(), is("two"));
+ }
+
+ @Test
+ public void selectGlobalProperty() {
+ setupData("selectGlobalProperties");
+
+ PropertyDto prop = dao.selectGlobalProperty("global.one");
+ assertThat(prop).isNotNull();
+ assertThat(prop.getValue(), is("one"));
+
+ assertThat(dao.selectGlobalProperty("project.one")).isNull();
+ assertThat(dao.selectGlobalProperty("user.one")).isNull();
+ assertThat(dao.selectGlobalProperty("unexisting")).isNull();
+ }
+
+ @Test
+ public void selectProjectProperties() {
+ setupData("selectProjectProperties");
+ List<PropertyDto> properties = dao.selectProjectProperties("org.struts:struts");
+ assertThat(properties.size(), is(1));
+
+ PropertyDto first = properties.get(0);
+ assertThat(first.getKey(), is("struts.one"));
+ assertThat(first.getValue(), is("one"));
+ }
+
+ @Test
+ public void select_module_properties_tree() {
+ setupData("select_module_properties_tree");
+
+ List<PropertyDto> properties = dao.selectEnabledDescendantModuleProperties("ABCD", session);
+ assertThat(properties.size(), is(4));
+ assertThat(properties).extracting("key").containsOnly("struts.one", "core.one", "core.two", "data.one");
+ assertThat(properties).extracting("value").containsOnly("one", "two");
+
+ properties = dao.selectEnabledDescendantModuleProperties("EFGH", session);
+ assertThat(properties.size(), is(3));
+ assertThat(properties).extracting("key").containsOnly("core.one", "core.two", "data.one");
+
+ properties = dao.selectEnabledDescendantModuleProperties("FGHI", session);
+ assertThat(properties.size(), is(1));
+ assertThat(properties).extracting("key").containsOnly("data.one");
+
+ assertThat(dao.selectEnabledDescendantModuleProperties("unknown", session).size(), is(0));
+ }
+
+ @Test
+ public void selectProjectProperty() {
+ setupData("selectProjectProperties");
+ PropertyDto property = dao.selectProjectProperty(11L, "commonslang.one");
+
+ assertThat(property.getKey(), is("commonslang.one"));
+ assertThat(property.getValue(), is("two"));
+ }
+
+ @Test
+ public void select_by_query() {
+ setupData("select_by_query");
+
+ List<PropertyDto> results = dao.selectByQuery(PropertyQuery.builder().setKey("user.two").setComponentId(10L).setUserId(100).build(), session);
+ assertThat(results).hasSize(1);
+ assertThat(results.get(0).getValue()).isEqualTo("two");
+
+ results = dao.selectByQuery(PropertyQuery.builder().setKey("user.one").setUserId(100).build(), session);
+ assertThat(results).hasSize(1);
+ assertThat(results.get(0).getValue()).isEqualTo("one");
+ }
+
+ @Test
+ public void setProperty_update() {
+ setupData("update");
+
+ dao.setProperty(new PropertyDto().setKey("global.key").setValue("new_global"));
+ dao.setProperty(new PropertyDto().setKey("project.key").setResourceId(10L).setValue("new_project"));
+ dao.setProperty(new PropertyDto().setKey("user.key").setUserId(100L).setValue("new_user"));
+ dao.setProperty(new PropertyDto().setKey("null.value").setValue(null));
+
+ checkTables("update", "properties");
+ }
+
+ @Test
+ public void setProperty_insert() {
+ setupData("insert");
+
+ dao.setProperty(new PropertyDto().setKey("global.key").setValue("new_global"));
+ dao.setProperty(new PropertyDto().setKey("project.key").setResourceId(10L).setValue("new_project"));
+ dao.setProperty(new PropertyDto().setKey("user.key").setUserId(100L).setValue("new_user"));
+
+ checkTables("insert", "properties");
+ }
+
+ @Test
+ public void delete_project_property() {
+ setupData("delete_project_property");
+
+ dao.deleteProjectProperty("struts.one", 10L);
+
+ checkTables("delete_project_property", "properties");
+ }
+
+ @Test
+ public void delete_project_properties() {
+ setupData("delete_project_properties");
+
+ dao.deleteProjectProperties("sonar.profile.java", "Sonar Way");
+
+ checkTables("delete_project_properties", "properties");
+ }
+
+ @Test
+ public void deleteGlobalProperties() {
+ setupData("deleteGlobalProperties");
+
+ dao.deleteGlobalProperties();
+
+ checkTables("deleteGlobalProperties", "properties");
+ }
+
+ @Test
+ public void deleteGlobalProperty() {
+ setupData("deleteGlobalProperty");
+
+ dao.deleteGlobalProperty("to_be_deleted");
+
+ checkTables("deleteGlobalProperty", "properties");
+ }
+
+ @Test
+ public void deleteAllProperties() {
+ setupData("deleteAllProperties");
+
+ dao.deleteAllProperties("to_be_deleted");
+
+ checkTables("deleteAllProperties", "properties");
+ }
+
+ @Test
+ public void insertGlobalProperties() {
+ setupData("insertGlobalProperties");
+
+ dao.saveGlobalProperties(ImmutableMap.of("to_be_inserted", "inserted"));
+
+ checkTable("insertGlobalProperties", "properties", "prop_key", "text_value", "resource_id", "user_id");
+ }
+
+ @Test
+ public void updateGlobalProperties() {
+ setupData("updateGlobalProperties");
+
+ dao.saveGlobalProperties(ImmutableMap.of("to_be_updated", "updated"));
+
+ checkTable("updateGlobalProperties", "properties", "prop_key", "text_value", "resource_id", "user_id");
+ }
+
+ @Test
+ public void renamePropertyKey() {
+ setupData("renamePropertyKey");
+
+ dao.renamePropertyKey("sonar.license.secured", "sonar.license");
+
+ checkTable("renamePropertyKey", "properties", "prop_key", "text_value", "resource_id", "user_id");
+ }
+
+ @Test
+ public void should_not_rename_if_same_key() {
+ setupData("should_not_rename_if_same_key");
+
+ dao.renamePropertyKey("foo", "foo");
+
+ checkTable("should_not_rename_if_same_key", "properties", "prop_key", "text_value", "resource_id", "user_id");
+ }
+
+ @Test
+ public void should_not_rename_with_empty_key() {
+ thrown.expect(IllegalArgumentException.class);
+ dao.renamePropertyKey("foo", "");
+ }
+
+ @Test
+ public void should_not_rename_an_empty_key() {
+ thrown.expect(IllegalArgumentException.class);
+ dao.renamePropertyKey(null, "foo");
+ }
+
+ @Test
+ public void updatePropertiesFromKeyAndValueToNewValue() {
+ setupData("updatePropertiesFromKeyAndValueToNewValue");
+
+ dao.updateProperties("sonar.profile.java", "Sonar Way", "Default");
+
+ checkTable("updatePropertiesFromKeyAndValueToNewValue", "properties", "prop_key", "text_value", "resource_id", "user_id");
+ }
+
+ private PropertyDto findById(List<PropertyDto> properties, int id) {
+ for (PropertyDto property : properties) {
+ if (property.getId() == id) {
+ return property;
+ }
+ }
+ return null;
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertyDtoTest.java b/sonar-db/src/test/java/org/sonar/db/property/PropertyDtoTest.java
new file mode 100644
index 00000000000..796260782b2
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/property/PropertyDtoTest.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.property;
+
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PropertyDtoTest extends AbstractDaoTestCase {
+
+ private PropertiesDao dao;
+
+ @Test
+ public void testEquals() {
+ assertThat(new PropertyDto().setKey("123").setResourceId(123L)).isEqualTo(new PropertyDto().setKey("123").setResourceId(123L));
+ assertThat(new PropertyDto().setKey("1234").setResourceId(123L)).isNotEqualTo(new PropertyDto().setKey("123").setResourceId(123L));
+ assertThat(new PropertyDto().setKey("1234").setResourceId(123L)).isNotEqualTo(null);
+ assertThat(new PropertyDto().setKey("1234").setResourceId(123L)).isNotEqualTo(new Object());
+ }
+
+ @Test
+ public void testHashCode() {
+ assertThat(new PropertyDto().setKey("123").setResourceId(123L).hashCode()).isNotNull();
+ assertThat(new PropertyDto().setKey("123").setResourceId(123L).hashCode())
+ .isEqualTo(new PropertyDto().setKey("123").setResourceId(123L).hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertThat(new PropertyDto().setKey("foo:bar").setValue("value").setResourceId(123L).setUserId(456L).toString()).isEqualTo("PropertyDto{foo:bar, value, 123, 456}");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/DbCleanerTestUtils.java b/sonar-db/src/test/java/org/sonar/db/purge/DbCleanerTestUtils.java
new file mode 100644
index 00000000000..c148aab159d
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/DbCleanerTestUtils.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge;
+
+import org.sonar.api.utils.DateUtils;
+
+public final class DbCleanerTestUtils {
+
+ private DbCleanerTestUtils() {
+ }
+
+ public static PurgeableSnapshotDto createSnapshotWithDate(long snapshotId, String date) {
+ PurgeableSnapshotDto snapshot = new PurgeableSnapshotDto();
+ snapshot.setSnapshotId(snapshotId);
+ snapshot.setDate(DateUtils.parseDate(date).getTime());
+ return snapshot;
+ }
+
+ public static PurgeableSnapshotDto createSnapshotWithDateTime(long snapshotId, String datetime) {
+ PurgeableSnapshotDto snapshot = new PurgeableSnapshotDto();
+ snapshot.setSnapshotId(snapshotId);
+ snapshot.setDate(DateUtils.parseDateTime(datetime).getTime());
+ return snapshot;
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/IdUuidPairsTest.java b/sonar-db/src/test/java/org/sonar/db/purge/IdUuidPairsTest.java
new file mode 100644
index 00000000000..21e527c1765
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/IdUuidPairsTest.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge;
+
+import com.google.common.collect.Lists;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.test.TestUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IdUuidPairsTest {
+ @Test
+ public void extract_ids() {
+ List<IdUuidPair> idUuidPairList = Lists.newArrayList(new IdUuidPair(1L, "ABCD"), new IdUuidPair(2L, "EFGH"));
+
+ List<Long> ids = IdUuidPairs.ids(idUuidPairList);
+
+ assertThat(ids).containsOnly(1L, 2L);
+ }
+
+ @Test
+ public void is_non_instantiable() {
+ assertThat(TestUtils.hasOnlyPrivateConstructors(IdUuidPairs.class)).isTrue();
+ }
+
+ @Test
+ public void extract_uuids() {
+ List<IdUuidPair> idUuidPairList = Lists.newArrayList(new IdUuidPair(1L, "ABCD"), new IdUuidPair(2L, "EFGH"));
+
+ List<String> uuids = IdUuidPairs.uuids(idUuidPairList);
+
+ assertThat(uuids).containsOnly("ABCD", "EFGH");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java b/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java
new file mode 100644
index 00000000000..0da36e52b04
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java
@@ -0,0 +1,153 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import java.util.List;
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.MyBatis;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class PurgeCommandsTest extends AbstractDaoTestCase {
+
+ private PurgeProfiler profiler;
+
+ @Before
+ public void prepare() {
+ profiler = new PurgeProfiler();
+ }
+
+ /**
+ * Test that all related data is deleted.
+ */
+ @Test
+ public void shouldDeleteSnapshot() {
+ setupData("shouldDeleteSnapshot");
+
+ SqlSession session = getMyBatis().openSession();
+ try {
+ new PurgeCommands(session, profiler).deleteSnapshots(PurgeSnapshotQuery.create().setId(5L));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ checkTables("shouldDeleteSnapshot", "snapshots", "project_measures", "duplications_index", "events");
+ }
+
+ /**
+ * Test that SQL queries execution do not fail with a huge number of parameter
+ */
+ @Test
+ public void should_not_fail_when_deleting_huge_number_of_snapshots() {
+ SqlSession session = getMyBatis().openSession();
+ try {
+ new PurgeCommands(session, profiler).deleteSnapshots(getHugeNumberOfIds());
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ // The goal of this test is only to check that the query do no fail, not to check result
+ }
+
+ /**
+ * Test that all related data is purged.
+ */
+ @Test
+ public void shouldPurgeSnapshot() {
+ setupData("shouldPurgeSnapshot");
+
+ SqlSession session = getMyBatis().openSession();
+ try {
+ new PurgeCommands(session, profiler).purgeSnapshots(PurgeSnapshotQuery.create().setId(1L));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ checkTables("shouldPurgeSnapshot", "snapshots", "project_measures", "duplications_index", "events");
+ }
+
+ @Test
+ public void delete_wasted_measures_when_purging_snapshot() {
+ setupData("shouldDeleteWastedMeasuresWhenPurgingSnapshot");
+
+ SqlSession session = getMyBatis().openSession();
+ try {
+ new PurgeCommands(session, profiler).purgeSnapshots(PurgeSnapshotQuery.create().setId(1L));
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ checkTables("shouldDeleteWastedMeasuresWhenPurgingSnapshot", "project_measures");
+ }
+
+ /**
+ * Test that SQL queries execution do not fail with a huge number of parameter
+ */
+ @Test
+ public void should_not_fail_when_purging_huge_number_of_snapshots() {
+ SqlSession session = getMyBatis().openSession();
+ try {
+ new PurgeCommands(session, profiler).purgeSnapshots(getHugeNumberOfIds());
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ // The goal of this test is only to check that the query do no fail, not to check result
+ }
+
+ @Test
+ public void shouldDeleteResource() {
+ setupData("shouldDeleteResource");
+ try (SqlSession session = getMyBatis().openSession()) {
+ new PurgeCommands(session, profiler).deleteResources(newArrayList(new IdUuidPair(1L, "1")));
+ }
+
+ assertEmptyTables("projects", "snapshots", "events", "issues", "issue_changes", "authors");
+ }
+
+ /**
+ * Test that SQL queries execution do not fail with a huge number of parameter
+ */
+ @Test
+ public void should_not_fail_when_deleting_huge_number_of_resources() {
+ SqlSession session = getMyBatis().openSession();
+ try {
+ new PurgeCommands(session, profiler).deleteResources(getHugeNumberOfIdUuids());
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ // The goal of this test is only to check that the query do no fail, not to check result
+ }
+
+ private List<IdUuidPair> getHugeNumberOfIdUuids() {
+ List<IdUuidPair> hugeNbOfSnapshotIds = newArrayList();
+ for (long i = 0; i < 4500; i++) {
+ hugeNbOfSnapshotIds.add(new IdUuidPair(i, String.valueOf(i)));
+ }
+ return hugeNbOfSnapshotIds;
+ }
+
+ private List<Long> getHugeNumberOfIds() {
+ List<Long> hugeNbOfSnapshotIds = newArrayList();
+ for (long i = 0; i < 4500; i++) {
+ hugeNbOfSnapshotIds.add(i);
+ }
+ return hugeNbOfSnapshotIds;
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java b/sonar-db/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java
new file mode 100644
index 00000000000..a150c84ebd1
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import java.util.Date;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.core.config.PurgeConstants;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PurgeConfigurationTest {
+ @Test
+ public void should_delete_all_closed_issues() {
+ PurgeConfiguration conf = new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[0], 0);
+ assertThat(conf.maxLiveDateOfClosedIssues()).isNull();
+
+ conf = new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[0], -1);
+ assertThat(conf.maxLiveDateOfClosedIssues()).isNull();
+ }
+
+ @Test
+ public void should_delete_only_old_closed_issues() {
+ Date now = DateUtils.parseDate("2013-05-18");
+
+ PurgeConfiguration conf = new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[0], 30);
+ Date toDate = conf.maxLiveDateOfClosedIssues(now);
+
+ assertThat(toDate.getYear()).isEqualTo(113);// =2013
+ assertThat(toDate.getMonth()).isEqualTo(3); // means April
+ assertThat(toDate.getDate()).isEqualTo(18);
+ }
+
+ @Test
+ public void do_not_delete_directory_by_default() {
+ Settings settings = new Settings();
+ settings.setProperty(PurgeConstants.PROPERTY_CLEAN_DIRECTORY, false);
+ settings.setProperty(PurgeConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES, 5);
+ Date now = new Date();
+
+ PurgeConfiguration sut = PurgeConfiguration.newDefaultPurgeConfiguration(settings, new IdUuidPair(42L, "any-uuid"));
+
+ assertThat(sut.scopesWithoutHistoricalData()).contains(Scopes.FILE)
+ .doesNotContain(Scopes.DIRECTORY);
+ assertThat(sut.maxLiveDateOfClosedIssues(now)).isEqualTo(DateUtils.addDays(now, -5));
+ }
+
+ @Test
+ public void delete_directory_if_in_settings() {
+ Settings settings = new Settings();
+ settings.setProperty(PurgeConstants.PROPERTY_CLEAN_DIRECTORY, true);
+
+ PurgeConfiguration sut = PurgeConfiguration.newDefaultPurgeConfiguration(settings, new IdUuidPair(42L, "any-uuid"));
+
+ assertThat(sut.scopesWithoutHistoricalData()).contains(Scopes.DIRECTORY, Scopes.FILE);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/sonar-db/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
new file mode 100644
index 00000000000..592e42b406e
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
@@ -0,0 +1,159 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+import org.sonar.db.MyBatis;
+import org.sonar.db.component.ResourceDao;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PurgeDaoTest extends AbstractDaoTestCase {
+
+ private PurgeDao sut;
+ private System2 system2;
+ private DbSession dbSession;
+
+ private static PurgeableSnapshotDto getById(List<PurgeableSnapshotDto> snapshots, long id) {
+ for (PurgeableSnapshotDto snapshot : snapshots) {
+ if (snapshot.getSnapshotId() == id) {
+ return snapshot;
+ }
+ }
+ return null;
+ }
+
+ @Before
+ public void before() {
+ system2 = mock(System2.class);
+ when(system2.now()).thenReturn(1450000000000L);
+ dbSession = getMyBatis().openSession(false);
+
+ sut = new PurgeDao(getMyBatis(), new ResourceDao(getMyBatis(), system2), new PurgeProfiler(), system2);
+ }
+
+ @After
+ public void after() {
+ MyBatis.closeQuietly(dbSession);
+ }
+
+ @Test
+ public void shouldDeleteAbortedBuilds() {
+ setupData("shouldDeleteAbortedBuilds");
+ sut.purge(newConfigurationWith30Days(), PurgeListener.EMPTY);
+ checkTables("shouldDeleteAbortedBuilds", "snapshots");
+ }
+
+ @Test
+ public void should_purge_project() {
+ setupData("shouldPurgeProject");
+ sut.purge(newConfigurationWith30Days(), PurgeListener.EMPTY);
+ checkTables("shouldPurgeProject", "projects", "snapshots");
+ }
+
+ private PurgeConfiguration newConfigurationWith30Days() {
+ return new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[0], 30);
+ }
+
+ private PurgeConfiguration newConfigurationWith30Days(System2 system2) {
+ return new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[0], 30, system2);
+ }
+
+ @Test
+ public void delete_file_sources_of_disabled_resources() {
+ setupData("delete_file_sources_of_disabled_resources");
+ sut.purge(newConfigurationWith30Days(system2), PurgeListener.EMPTY);
+ checkTables("delete_file_sources_of_disabled_resources", "file_sources");
+ }
+
+ @Test
+ public void shouldDeleteHistoricalDataOfDirectoriesAndFiles() {
+ setupData("shouldDeleteHistoricalDataOfDirectoriesAndFiles");
+ sut.purge(new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[] {Scopes.DIRECTORY, Scopes.FILE}, 30), PurgeListener.EMPTY);
+ checkTables("shouldDeleteHistoricalDataOfDirectoriesAndFiles", "projects", "snapshots");
+ }
+
+ @Test
+ public void disable_resources_without_last_snapshot() {
+ setupData("disable_resources_without_last_snapshot");
+ sut.purge(newConfigurationWith30Days(system2), PurgeListener.EMPTY);
+ checkTables("disable_resources_without_last_snapshot", new String[] {"issue_close_date", "issue_update_date"}, "projects", "snapshots", "issues");
+ }
+
+ @Test
+ public void shouldDeleteSnapshots() {
+ setupData("shouldDeleteSnapshots");
+ sut.deleteSnapshots(PurgeSnapshotQuery.create().setIslast(false).setResourceId(1L));
+ checkTables("shouldDeleteSnapshots", "snapshots");
+ }
+
+ @Test
+ public void shouldSelectPurgeableSnapshots() {
+ setupData("shouldSelectPurgeableSnapshots");
+ List<PurgeableSnapshotDto> snapshots = sut.selectPurgeableSnapshots(1L);
+
+ assertThat(snapshots).hasSize(3);
+ assertThat(getById(snapshots, 1L).isLast()).isTrue();
+ assertThat(getById(snapshots, 1L).hasEvents()).isFalse();
+ assertThat(getById(snapshots, 4L).isLast()).isFalse();
+ assertThat(getById(snapshots, 4L).hasEvents()).isFalse();
+ assertThat(getById(snapshots, 5L).isLast()).isFalse();
+ assertThat(getById(snapshots, 5L).hasEvents()).isTrue();
+ }
+
+ @Test
+ public void should_delete_project_and_associated_data() {
+ setupData("shouldDeleteProject");
+ sut.deleteResourceTree(new IdUuidPair(1L, "A"));
+ assertEmptyTables("projects", "snapshots", "action_plans", "issues", "issue_changes", "file_sources");
+ }
+
+ @Test
+ public void should_delete_old_closed_issues() {
+ setupData("should_delete_old_closed_issues");
+ sut.purge(newConfigurationWith30Days(), PurgeListener.EMPTY);
+ checkTables("should_delete_old_closed_issues", "issues", "issue_changes");
+ }
+
+ @Test
+ public void should_delete_all_closed_issues() {
+ setupData("should_delete_all_closed_issues");
+ sut.purge(new PurgeConfiguration(new IdUuidPair(1L, "1"), new String[0], 0), PurgeListener.EMPTY);
+ checkTables("should_delete_all_closed_issues", "issues", "issue_changes");
+ }
+
+ @Test
+ public void select_purgeable_file_uuids_and_only_them() {
+ setupData("select_purgeable_file_uuids");
+
+ List<String> uuids = sut.selectPurgeableFiles(dbSession, 1L);
+
+ assertThat(uuids).containsOnly("GHIJ");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/PurgeProfilerTest.java b/sonar-db/src/test/java/org/sonar/db/purge/PurgeProfilerTest.java
new file mode 100644
index 00000000000..2823ee39d75
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/PurgeProfilerTest.java
@@ -0,0 +1,95 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.log.Logger;
+
+import static org.mockito.Matchers.contains;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class PurgeProfilerTest {
+
+ private MockedClock clock;
+ private PurgeProfiler profiler;
+ private Logger logger;
+
+ @Before
+ public void prepare() {
+ clock = new MockedClock();
+ profiler = new PurgeProfiler(clock);
+ logger = mock(Logger.class);
+ }
+
+ @Test
+ public void shouldProfilePurge() {
+ profiler.start("foo");
+ clock.sleep(10);
+ profiler.stop();
+
+ profiler.start("bar");
+ clock.sleep(5);
+ profiler.stop();
+
+ profiler.start("foo");
+ clock.sleep(8);
+ profiler.stop();
+
+ profiler.dump(50, logger);
+ verify(logger).info(contains("foo: 18ms"));
+ verify(logger).info(contains("bar: 5ms"));
+ }
+
+ @Test
+ public void shouldResetPurgeProfiling() {
+ profiler.start("foo");
+ clock.sleep(10);
+ profiler.stop();
+
+ profiler.reset();
+
+ profiler.start("bar");
+ clock.sleep(5);
+ profiler.stop();
+
+ profiler.start("foo");
+ clock.sleep(8);
+ profiler.stop();
+
+ profiler.dump(50, logger);
+ verify(logger).info(contains("foo: 8ms"));
+ verify(logger).info(contains("bar: 5ms"));
+ }
+
+ private class MockedClock extends PurgeProfiler.Clock {
+ private long now = 0;
+
+ @Override
+ public long now() {
+ return now;
+ }
+
+ public void sleep(long duration) {
+ now += duration;
+ }
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/PurgeableSnapshotDtoTest.java b/sonar-db/src/test/java/org/sonar/db/purge/PurgeableSnapshotDtoTest.java
new file mode 100644
index 00000000000..ec61f04f4fd
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/PurgeableSnapshotDtoTest.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.purge;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class PurgeableSnapshotDtoTest {
+ @Test
+ public void testEquals() {
+ PurgeableSnapshotDto dto1 = new PurgeableSnapshotDto().setSnapshotId(3L);
+ PurgeableSnapshotDto dto2 = new PurgeableSnapshotDto().setSnapshotId(4L);
+ assertThat(dto1.equals(dto2), is(false));
+ assertThat(dto2.equals(dto1), is(false));
+ assertThat(dto1.equals(dto1), is(true));
+ assertThat(dto1.equals(new PurgeableSnapshotDto().setSnapshotId(3L)), is(true));
+ assertThat(dto1.equals("bi_bop_a_lou_la"), is(false));
+ assertThat(dto1.equals(null), is(false));
+ }
+
+ @Test
+ public void testHasCode() {
+ PurgeableSnapshotDto dto = new PurgeableSnapshotDto().setSnapshotId(3L);
+ assertThat(dto.hashCode(), is(dto.hashCode()));
+
+ // no id
+ dto = new PurgeableSnapshotDto();
+ assertThat(dto.hashCode(), is(dto.hashCode()));
+ }
+
+ @Test
+ public void testToString() {
+ PurgeableSnapshotDto dto = new PurgeableSnapshotDto().setSnapshotId(3L);
+ assertThat(dto.toString().length(), greaterThan(0));
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/period/DefaultPeriodCleanerTest.java b/sonar-db/src/test/java/org/sonar/db/purge/period/DefaultPeriodCleanerTest.java
new file mode 100644
index 00000000000..1aa9d8aebcb
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/period/DefaultPeriodCleanerTest.java
@@ -0,0 +1,93 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import java.util.Arrays;
+import org.apache.commons.lang.ObjectUtils;
+import org.hamcrest.BaseMatcher;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.purge.PurgeDao;
+import org.sonar.db.purge.PurgeSnapshotQuery;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+import static org.mockito.Mockito.anyListOf;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultPeriodCleanerTest {
+
+ @Test
+ public void doClean() {
+ PurgeDao dao = mock(PurgeDao.class);
+ DbSession session = mock(DbSession.class);
+ when(dao.selectPurgeableSnapshots(123L, session)).thenReturn(Arrays.asList(
+ new PurgeableSnapshotDto().setSnapshotId(999L).setDate(System2.INSTANCE.now())));
+ Filter filter1 = newLazyFilter();
+ Filter filter2 = newLazyFilter();
+
+ DefaultPeriodCleaner cleaner = new DefaultPeriodCleaner(dao);
+ cleaner.doClean(123L, Arrays.asList(filter1, filter2), session);
+
+ verify(filter1).log();
+ verify(filter2).log();
+ verify(dao, times(2)).deleteSnapshots(argThat(newRootSnapshotQuery()), eq(session));
+ verify(dao, times(2)).deleteSnapshots(argThat(newSnapshotIdQuery()), eq(session));
+ }
+
+ private BaseMatcher<PurgeSnapshotQuery> newRootSnapshotQuery() {
+ return new ArgumentMatcher<PurgeSnapshotQuery>() {
+ @Override
+ public boolean matches(Object o) {
+ PurgeSnapshotQuery query = (PurgeSnapshotQuery) o;
+ return ObjectUtils.equals(query.getRootSnapshotId(), 999L);
+ }
+ };
+ }
+
+ private BaseMatcher<PurgeSnapshotQuery> newSnapshotIdQuery() {
+ return new ArgumentMatcher<PurgeSnapshotQuery>() {
+ @Override
+ public boolean matches(Object o) {
+ PurgeSnapshotQuery query = (PurgeSnapshotQuery) o;
+ return ObjectUtils.equals(query.getId(), 999L);
+ }
+ };
+ }
+
+ private Filter newLazyFilter() {
+ Filter filter1 = mock(Filter.class);
+ when(filter1.filter(anyListOf(PurgeableSnapshotDto.class))).thenAnswer(new Answer<Object>() {
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return invocation.getArguments()[0];
+ }
+ });
+ return filter1;
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/period/DeleteAllFilterTest.java b/sonar-db/src/test/java/org/sonar/db/purge/period/DeleteAllFilterTest.java
new file mode 100644
index 00000000000..7f59cb68a8c
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/period/DeleteAllFilterTest.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.purge.DbCleanerTestUtils;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DeleteAllFilterTest {
+
+ @Test
+ public void shouldDeleteAllSnapshotsPriorToDate() {
+ Filter filter = new DeleteAllFilter(DateUtils.parseDate("2011-12-25"));
+
+ List<PurgeableSnapshotDto> toDelete = filter.filter(Arrays.asList(
+ DbCleanerTestUtils.createSnapshotWithDate(1L, "2010-01-01"),
+ DbCleanerTestUtils.createSnapshotWithDate(2L, "2010-12-25"),
+ DbCleanerTestUtils.createSnapshotWithDate(3L, "2012-01-01")
+ ));
+
+ assertThat(toDelete).extracting("snapshotId").containsOnly(1L, 2L);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/period/IntervalTest.java b/sonar-db/src/test/java/org/sonar/db/purge/period/IntervalTest.java
new file mode 100644
index 00000000000..dbae556bfa4
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/period/IntervalTest.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.purge.DbCleanerTestUtils;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class IntervalTest {
+ static int calendarField(Interval interval, int field) {
+ if (interval.count() == 0) {
+ return -1;
+ }
+
+ PurgeableSnapshotDto first = interval.get().iterator().next();
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.setTime(first.getDate());
+ return cal.get(field);
+ }
+
+ @Test
+ public void shouldGroupByIntervals() {
+ List<PurgeableSnapshotDto> snapshots = Arrays.asList(
+ DbCleanerTestUtils.createSnapshotWithDate(1L, "2011-04-03"),
+
+ DbCleanerTestUtils.createSnapshotWithDate(2L, "2011-05-01"),
+ DbCleanerTestUtils.createSnapshotWithDate(3L, "2011-05-19"),
+
+ DbCleanerTestUtils.createSnapshotWithDate(4L, "2011-06-02"),
+ DbCleanerTestUtils.createSnapshotWithDate(5L, "2011-06-20"),
+
+ DbCleanerTestUtils.createSnapshotWithDate(6L, "2012-06-29") // out of scope
+ );
+
+ List<Interval> intervals = Interval.group(snapshots, DateUtils.parseDate("2010-01-01"), DateUtils.parseDate("2011-12-31"), Calendar.MONTH);
+ assertThat(intervals.size(), is(3));
+
+ assertThat(intervals.get(0).count(), is(1));
+ assertThat(calendarField(intervals.get(0), Calendar.MONTH), is(Calendar.APRIL));
+
+ assertThat(intervals.get(1).count(), is(2));
+ assertThat(calendarField(intervals.get(1), Calendar.MONTH), is(Calendar.MAY));
+
+ assertThat(intervals.get(2).count(), is(2));
+ assertThat(calendarField(intervals.get(2), Calendar.MONTH), is(Calendar.JUNE));
+ }
+
+ @Test
+ public void shouldNotJoinMonthsOfDifferentYears() {
+ List<PurgeableSnapshotDto> snapshots = Arrays.asList(
+ DbCleanerTestUtils.createSnapshotWithDate(1L, "2010-04-03"),
+ DbCleanerTestUtils.createSnapshotWithDate(2L, "2011-04-13")
+ );
+
+ List<Interval> intervals = Interval.group(snapshots,
+ DateUtils.parseDateTime("2010-01-01T00:00:00+0100"), DateUtils.parseDateTime("2011-12-31T00:00:00+0100"), Calendar.MONTH);
+ assertThat(intervals.size(), is(2));
+
+ assertThat(intervals.get(0).count(), is(1));
+ assertThat(calendarField(intervals.get(0), Calendar.MONTH), is(Calendar.APRIL));
+ assertThat(calendarField(intervals.get(0), Calendar.YEAR), is(2010));
+
+ assertThat(intervals.get(1).count(), is(1));
+ assertThat(calendarField(intervals.get(1), Calendar.MONTH), is(Calendar.APRIL));
+ assertThat(calendarField(intervals.get(1), Calendar.YEAR), is(2011));
+ }
+
+ @Test
+ public void shouldIgnoreTimeWhenGroupingByIntervals() {
+ List<PurgeableSnapshotDto> snapshots = Arrays.asList(
+ DbCleanerTestUtils.createSnapshotWithDateTime(1L, "2011-05-25T00:16:48+0100"),
+ DbCleanerTestUtils.createSnapshotWithDateTime(2L, "2012-01-26T00:16:48+0100"),
+ DbCleanerTestUtils.createSnapshotWithDateTime(3L, "2012-01-27T00:16:48+0100")
+ );
+
+ List<Interval> intervals = Interval.group(snapshots,
+ DateUtils.parseDateTime("2011-05-25T00:00:00+0100"),
+ DateUtils.parseDateTime("2012-01-26T00:00:00+0100"), Calendar.MONTH);
+ assertThat(intervals.size(), is(1));
+ assertThat(intervals.get(0).count(), is(1));
+ assertThat(intervals.get(0).get().get(0).getSnapshotId(), is(2L));
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/purge/period/KeepOneFilterTest.java b/sonar-db/src/test/java/org/sonar/db/purge/period/KeepOneFilterTest.java
new file mode 100644
index 00000000000..94845f431ae
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/purge/period/KeepOneFilterTest.java
@@ -0,0 +1,93 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.purge.period;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.purge.DbCleanerTestUtils;
+import org.sonar.db.purge.PurgeableSnapshotDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class KeepOneFilterTest {
+
+ private static List<Long> snapshotIds(List<PurgeableSnapshotDto> snapshotDtos) {
+ return newArrayList(Iterables.transform(snapshotDtos, new Function<PurgeableSnapshotDto, Long>() {
+ @Override
+ public Long apply(@Nullable PurgeableSnapshotDto input) {
+ return input != null ? input.getSnapshotId() : null;
+ }
+ }));
+ }
+
+ @Test
+ public void shouldOnlyOneSnapshotPerInterval() {
+ Filter filter = new KeepOneFilter(DateUtils.parseDate("2011-03-25"), DateUtils.parseDate("2011-08-25"), Calendar.MONTH, "month");
+
+ List<PurgeableSnapshotDto> toDelete = filter.filter(Arrays.<PurgeableSnapshotDto>asList(
+ DbCleanerTestUtils.createSnapshotWithDate(1L, "2010-01-01"), // out of scope -> keep
+ DbCleanerTestUtils.createSnapshotWithDate(2L, "2011-05-01"), // may -> keep
+ DbCleanerTestUtils.createSnapshotWithDate(3L, "2011-05-02"), // may -> to be deleted
+ DbCleanerTestUtils.createSnapshotWithDate(4L, "2011-05-19"), // may -> to be deleted
+ DbCleanerTestUtils.createSnapshotWithDate(5L, "2011-06-01"), // june -> keep
+ DbCleanerTestUtils.createSnapshotWithDate(6L, "2012-01-01") // out of scope -> keep
+ ));
+
+ assertThat(toDelete).hasSize(2);
+
+ List<Long> snapshotIds = snapshotIds(toDelete);
+ assertThat(snapshotIds).contains(3L);
+ assertThat(snapshotIds.contains(4L));
+ }
+
+ @Test
+ public void shouldKeepNonDeletableSnapshots() {
+ Filter filter = new KeepOneFilter(DateUtils.parseDate("2011-03-25"), DateUtils.parseDate("2011-08-25"), Calendar.MONTH, "month");
+
+ List<PurgeableSnapshotDto> toDelete = filter.filter(Arrays.<PurgeableSnapshotDto>asList(
+ DbCleanerTestUtils.createSnapshotWithDate(1L, "2011-05-01"), // to be deleted
+ DbCleanerTestUtils.createSnapshotWithDate(2L, "2011-05-02").setLast(true),
+ DbCleanerTestUtils.createSnapshotWithDate(3L, "2011-05-19").setHasEvents(true).setLast(false),
+ DbCleanerTestUtils.createSnapshotWithDate(4L, "2011-05-23") // to be deleted
+ ));
+
+ assertThat(toDelete).hasSize(2);
+
+ List<Long> snapshotIds = snapshotIds(toDelete);
+ assertThat(snapshotIds).contains(1L);
+ assertThat(snapshotIds.contains(4L));
+ }
+
+ @Test
+ public void test_isDeletable() {
+ assertThat(KeepOneFilter.isDeletable(DbCleanerTestUtils.createSnapshotWithDate(1L, "2011-05-01"))).isTrue();
+ assertThat(KeepOneFilter.isDeletable(DbCleanerTestUtils.createSnapshotWithDate(1L, "2011-05-01").setLast(true))).isFalse();
+ assertThat(KeepOneFilter.isDeletable(DbCleanerTestUtils.createSnapshotWithDate(1L, "2011-05-01").setHasEvents(true))).isFalse();
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java b/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java
new file mode 100644
index 00000000000..e9fc43db574
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java
@@ -0,0 +1,83 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualitygate;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectQgateAssociationDaoTest extends AbstractDaoTestCase {
+
+ private ProjectQgateAssociationDao dao;
+
+ @Before
+ public void setUp() {
+ dao = new ProjectQgateAssociationDao(getMyBatis());
+ }
+
+ @Test
+ public void select_all_projects_by_query() {
+ setupData("shared");
+
+ ProjectQgateAssociationQuery query = ProjectQgateAssociationQuery.builder().gateId("42").build();
+ List<ProjectQgateAssociationDto> result = dao.selectProjects(query, 42L);
+ assertThat(result).hasSize(5);
+ }
+
+ @Test
+ public void select_projects_by_query() {
+ setupData("shared");
+
+ assertThat(dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").membership(ProjectQgateAssociationQuery.IN).build(), 42L)).hasSize(3);
+ assertThat(dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").membership(ProjectQgateAssociationQuery.OUT).build(), 42L)).hasSize(2);
+ }
+
+ @Test
+ public void search_by_project_name() {
+ setupData("shared");
+
+ List<ProjectQgateAssociationDto> result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("one").build(), 42L);
+ assertThat(result).hasSize(1);
+
+ assertThat(result.get(0).getName()).isEqualTo("Project One");
+
+ result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("one").build(), 42L);
+ assertThat(result).hasSize(1);
+ result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").projectSearch("project").build(), 42L);
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ public void should_be_sorted_by_project_name() {
+ setupData("shared");
+
+ List<ProjectQgateAssociationDto> result = dao.selectProjects(ProjectQgateAssociationQuery.builder().gateId("42").build(), 42L);
+ assertThat(result).hasSize(5);
+ assertThat(result.get(0).getName()).isEqualTo("Project Five");
+ assertThat(result.get(1).getName()).isEqualTo("Project Four");
+ assertThat(result.get(2).getName()).isEqualTo("Project One");
+ assertThat(result.get(3).getName()).isEqualTo("Project Three");
+ assertThat(result.get(4).getName()).isEqualTo("Project Two");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java b/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java
new file mode 100644
index 00000000000..2b51c8f450c
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDtoTest.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualitygate;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectQgateAssociationDtoTest {
+
+ @Test
+ public void to_assoc_with_project_having_assoc() {
+ ProjectQgateAssociation project = new ProjectQgateAssociationDto()
+ .setId(1L)
+ .setName("polop")
+ .setGateId("10")
+ .toQgateAssociation();
+
+ assertThat(project.id()).isEqualTo(1);
+ assertThat(project.name()).isEqualTo("polop");
+ assertThat(project.isMember()).isTrue();
+ }
+
+ @Test
+ public void to_assoc_with_project_not_having_assoc() {
+ ProjectQgateAssociation project = new ProjectQgateAssociationDto()
+ .setId(1L)
+ .setName("polop")
+ .setGateId(null)
+ .toQgateAssociation();
+
+ assertThat(project.id()).isEqualTo(1);
+ assertThat(project.name()).isEqualTo("polop");
+ assertThat(project.isMember()).isFalse();
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationQueryTest.java b/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationQueryTest.java
new file mode 100644
index 00000000000..e8b12e11569
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationQueryTest.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualitygate;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class ProjectQgateAssociationQueryTest {
+
+ @Test
+ public void fail_on_null_login() {
+ ProjectQgateAssociationQuery.Builder builder = ProjectQgateAssociationQuery.builder();
+ builder.gateId(null);
+
+ try {
+ builder.build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(NullPointerException.class).hasMessage("Gate ID cant be null.");
+ }
+ }
+
+ @Test
+ public void fail_on_invalid_membership() {
+ ProjectQgateAssociationQuery.Builder builder = ProjectQgateAssociationQuery.builder();
+ builder.gateId("nelson");
+ builder.membership("unknwown");
+
+ try {
+ builder.build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Membership is not valid (got unknwown). Availables values are [all, selected, deselected]");
+ }
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDaoTest.java b/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDaoTest.java
new file mode 100644
index 00000000000..8e2840e09df
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDaoTest.java
@@ -0,0 +1,92 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QualityGateConditionDaoTest extends AbstractDaoTestCase {
+
+ private static final String[] COLUMNS_WITHOUT_TIMESTAMPS = {
+ "id", "qgate_id", "metric_id", "operator", "value_warning", "value_error", "period"
+ };
+
+ private static QualityGateConditionDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new QualityGateConditionDao(getMyBatis());
+ }
+
+ @Test
+ public void testInsert() throws Exception {
+ setupData("insert");
+ QualityGateConditionDto newCondition = new QualityGateConditionDto()
+ .setQualityGateId(1L).setMetricId(2L).setOperator("GT").setWarningThreshold("10").setErrorThreshold("20").setPeriod(3);
+ dao.insert(newCondition);
+ checkTable("insert", "quality_gate_conditions", "metric_id", "operator", "error_value", "warning_value", "period");
+ assertThat(newCondition.getId()).isNotNull();
+ }
+
+ @Test
+ public void testSelectForQualityGate() throws Exception {
+ setupData("selectForQualityGate");
+ assertThat(dao.selectForQualityGate(1L)).hasSize(3);
+ assertThat(dao.selectForQualityGate(2L)).hasSize(2);
+ }
+
+ @Test
+ public void testSelectById() throws Exception {
+ setupData("selectForQualityGate");
+ QualityGateConditionDto selectById = dao.selectById(1L);
+ assertThat(selectById).isNotNull();
+ assertThat(selectById.getId()).isNotNull().isNotEqualTo(0L);
+ assertThat(selectById.getMetricId()).isEqualTo(2L);
+ assertThat(selectById.getOperator()).isEqualTo("<");
+ assertThat(selectById.getPeriod()).isEqualTo(3);
+ assertThat(selectById.getQualityGateId()).isEqualTo(1L);
+ assertThat(selectById.getWarningThreshold()).isEqualTo("10");
+ assertThat(selectById.getErrorThreshold()).isEqualTo("20");
+ assertThat(dao.selectById(42L)).isNull();
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ setupData("selectForQualityGate");
+ dao.delete(new QualityGateConditionDto().setId(1L));
+ checkTable("delete", "quality_gate_conditions", COLUMNS_WITHOUT_TIMESTAMPS);
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ setupData("selectForQualityGate");
+ dao.update(new QualityGateConditionDto().setId(1L).setMetricId(7L).setOperator(">").setPeriod(1).setWarningThreshold("50").setErrorThreshold("80"));
+ checkTable("update", "quality_gate_conditions", COLUMNS_WITHOUT_TIMESTAMPS);
+ }
+
+ public void shouldCleanConditions() {
+ setupData("shouldCleanConditions");
+ dao.deleteConditionsWithInvalidMetrics();
+ checkTables("shouldCleanConditions", "quality_gate_conditions");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDtoTest.java b/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDtoTest.java
new file mode 100644
index 00000000000..84e7bbc46df
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateConditionDtoTest.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import org.junit.Test;
+import org.sonar.api.measures.Metric.ValueType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.Metric.ValueType.BOOL;
+import static org.sonar.api.measures.Metric.ValueType.DATA;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.LEVEL;
+import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
+import static org.sonar.api.measures.Metric.ValueType.PERCENT;
+import static org.sonar.api.measures.Metric.ValueType.RATING;
+import static org.sonar.api.measures.Metric.ValueType.STRING;
+import static org.sonar.db.qualitygate.QualityGateConditionDto.isOperatorAllowed;
+
+public class QualityGateConditionDtoTest {
+
+ @Test
+ public void should_validate_operators_for_metric_type() {
+ assertThat(isOperatorAllowed("WHATEVER", null)).isFalse();
+ assertThat(isOperatorAllowed("WHATEVER", DATA)).isFalse();
+
+ assertThat(isOperatorAllowed("EQ", BOOL)).isTrue();
+ assertThat(isOperatorAllowed("NE", BOOL)).isFalse();
+ assertThat(isOperatorAllowed("LT", BOOL)).isFalse();
+ assertThat(isOperatorAllowed("GT", BOOL)).isFalse();
+
+ assertThat(isOperatorAllowed("EQ", LEVEL)).isTrue();
+ assertThat(isOperatorAllowed("NE", LEVEL)).isTrue();
+ assertThat(isOperatorAllowed("LT", LEVEL)).isFalse();
+ assertThat(isOperatorAllowed("GT", LEVEL)).isFalse();
+
+ for (ValueType type : new ValueType[] {STRING, INT, FLOAT, PERCENT, MILLISEC, RATING}) {
+ assertThat(isOperatorAllowed("EQ", type)).isTrue();
+ assertThat(isOperatorAllowed("NE", type)).isTrue();
+ assertThat(isOperatorAllowed("LT", type)).isTrue();
+ assertThat(isOperatorAllowed("GT", type)).isTrue();
+ }
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateDaoTest.java b/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateDaoTest.java
new file mode 100644
index 00000000000..707bd159e5e
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualitygate/QualityGateDaoTest.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualitygate;
+
+import java.util.Collection;
+import java.util.Iterator;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QualityGateDaoTest extends AbstractDaoTestCase {
+
+ private static QualityGateDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new QualityGateDao(getMyBatis());
+ }
+
+ @Test
+ public void testInsert() throws Exception {
+ setupData("insert");
+ QualityGateDto newQgate = new QualityGateDto().setName("My Quality Gate");
+ dao.insert(newQgate);
+ checkTable("insert", "quality_gates", "name");
+ assertThat(newQgate.getId()).isNotNull();
+ }
+
+ @Test
+ public void testSelectAll() throws Exception {
+ setupData("selectAll");
+ Collection<QualityGateDto> allQualityGates = dao.selectAll();
+ assertThat(allQualityGates).hasSize(3);
+ Iterator<QualityGateDto> gatesIterator = allQualityGates.iterator();
+ assertThat(gatesIterator.next().getName()).isEqualTo("Balanced");
+ assertThat(gatesIterator.next().getName()).isEqualTo("Lenient");
+ assertThat(gatesIterator.next().getName()).isEqualTo("Very strict");
+ }
+
+ @Test
+ public void testSelectByName() throws Exception {
+ setupData("selectAll");
+ assertThat(dao.selectByName("Balanced").getName()).isEqualTo("Balanced");
+ assertThat(dao.selectByName("Unknown")).isNull();
+ }
+
+ @Test
+ public void testSelectById() throws Exception {
+ setupData("selectAll");
+ assertThat(dao.selectById(1L).getName()).isEqualTo("Very strict");
+ assertThat(dao.selectById(42L)).isNull();
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ setupData("selectAll");
+ dao.delete(new QualityGateDto().setId(1L));
+ checkTable("delete", "quality_gates", "id", "name");
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ setupData("selectAll");
+ dao.update(new QualityGateDto().setId(1L).setName("Not so strict"));
+ checkTable("update", "quality_gates", "id", "name");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleDaoTest.java b/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleDaoTest.java
new file mode 100644
index 00000000000..7c3eeaa1d08
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleDaoTest.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ActiveRuleDaoTest extends AbstractDaoTestCase {
+
+ ActiveRuleDao dao;
+
+ @Before
+ public void before() {
+ dao = new ActiveRuleDao(getMyBatis());
+ }
+
+ @Test
+ public void select_by_profile() {
+ setupData("shared");
+
+ List<ActiveRuleDto> result = dao.selectByProfileKey("parent");
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ public void insert_parameter() {
+ setupData("empty");
+
+ DbSession session = getMyBatis().openSession(false);
+ ActiveRuleParamDto dto = new ActiveRuleParamDto()
+ .setActiveRuleId(1)
+ .setRulesParameterId(1)
+ .setKey("max")
+ .setValue("20");
+ dao.insert(dto, session);
+ session.commit();
+ session.close();
+
+ checkTables("insert_parameter", "active_rule_parameters");
+ }
+
+ @Test
+ public void select_params_by_profile_id() {
+ setupData("shared");
+
+ assertThat(dao.selectParamsByProfileKey("child")).hasSize(2);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleKeyTest.java b/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleKeyTest.java
new file mode 100644
index 00000000000..e89f7d81213
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleKeyTest.java
@@ -0,0 +1,101 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class ActiveRuleKeyTest {
+
+ @Test
+ public void of() {
+ RuleKey ruleKey = RuleKey.of("xoo", "R1");
+ ActiveRuleKey key = ActiveRuleKey.of("P1", ruleKey);
+ assertThat(key.qProfile()).isEqualTo("P1");
+ assertThat(key.ruleKey()).isSameAs(ruleKey);
+ assertThat(key.toString()).isEqualTo("P1:xoo:R1");
+ }
+
+ @Test
+ public void rule_key_can_contain_colons() {
+ RuleKey ruleKey = RuleKey.of("squid", "Key:With:Some::Colons");
+ ActiveRuleKey key = ActiveRuleKey.of("P1", ruleKey);
+ assertThat(key.qProfile()).isEqualTo("P1");
+ assertThat(key.ruleKey()).isSameAs(ruleKey);
+ assertThat(key.toString()).isEqualTo("P1:squid:Key:With:Some::Colons");
+ }
+
+ @Test
+ public void profile_must_not_be_null() {
+ try {
+ ActiveRuleKey.of(null, RuleKey.of("xoo", "R1"));
+ fail();
+ } catch (NullPointerException e) {
+ assertThat(e).hasMessage("QProfile is missing");
+ }
+ }
+
+ @Test
+ public void rule_key_must_not_be_null() {
+ try {
+ ActiveRuleKey.of("P1", null);
+ fail();
+ } catch (NullPointerException e) {
+ assertThat(e).hasMessage("RuleKey is missing");
+ }
+ }
+
+ @Test
+ public void parse() {
+ ActiveRuleKey key = ActiveRuleKey.parse("P1:xoo:R1");
+ assertThat(key.qProfile()).isEqualTo("P1");
+ assertThat(key.ruleKey().repository()).isEqualTo("xoo");
+ assertThat(key.ruleKey().rule()).isEqualTo("R1");
+ }
+
+ @Test
+ public void parse_fail_when_less_than_three_colons() {
+ try {
+ ActiveRuleKey.parse("P1:xoo");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Bad format of activeRule key: P1:xoo");
+ }
+ }
+
+ @Test
+ public void equals_and_hashcode() {
+ ActiveRuleKey key1 = ActiveRuleKey.parse("P1:xoo:R1");
+ ActiveRuleKey key1b = ActiveRuleKey.parse("P1:xoo:R1");
+ ActiveRuleKey key2 = ActiveRuleKey.parse("P1:xoo:R2");
+ ActiveRuleKey key3 = ActiveRuleKey.parse("P2:xoo:R1");
+ assertThat(key1.equals(key1)).isTrue();
+ assertThat(key1.equals(key1b)).isTrue();
+ assertThat(key1.equals(null)).isFalse();
+ assertThat(key1.equals("P1:xoo:R1")).isFalse();
+ assertThat(key1.equals(key2)).isFalse();
+ assertThat(key1.equals(key3)).isFalse();
+
+ assertThat(key1.hashCode()).isEqualTo(key1.hashCode());
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleParamDtoTest.java b/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleParamDtoTest.java
new file mode 100644
index 00000000000..43a5ff98305
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualityprofile/ActiveRuleParamDtoTest.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.qualityprofile;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ActiveRuleParamDtoTest {
+
+ @Test
+ public void groupByKey() {
+ assertThat(ActiveRuleParamDto.groupByKey(Collections.<ActiveRuleParamDto>emptyList())).isEmpty();
+
+ Collection<ActiveRuleParamDto> dtos = Arrays.asList(
+ new ActiveRuleParamDto().setKey("foo"), new ActiveRuleParamDto().setKey("bar")
+ );
+ Map<String, ActiveRuleParamDto> group = ActiveRuleParamDto.groupByKey(dtos);
+ assertThat(group.keySet()).containsOnly("foo", "bar");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java b/sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java
new file mode 100644
index 00000000000..a70307ea49e
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java
@@ -0,0 +1,255 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.qualityprofile;
+
+import java.util.List;
+import org.assertj.core.data.MapEntry;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UtcDateUtils;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class QualityProfileDaoTest extends AbstractDaoTestCase {
+
+ QualityProfileDao dao;
+ DbSession session;
+ System2 system = mock(System2.class);
+
+ @Before
+ public void createDao() {
+ this.session = getMyBatis().openSession(false);
+ dao = new QualityProfileDao(getMyBatis(), system);
+ when(system.now()).thenReturn(UtcDateUtils.parseDateTime("2014-01-20T12:00:00+0000").getTime());
+ }
+
+ @After
+ public void after() {
+ this.session.close();
+ }
+
+ @Test
+ public void insert() {
+ setupData("shared");
+
+ QualityProfileDto dto = QualityProfileDto.createFor("abcde")
+ .setName("ABCDE")
+ .setLanguage("xoo");
+
+ dao.insert(dto);
+
+ checkTables("insert", new String[] {"created_at", "updated_at", "rules_updated_at"}, "rules_profiles");
+ }
+
+ @Test
+ public void update() {
+ setupData("shared");
+
+ QualityProfileDto dto = new QualityProfileDto()
+ .setId(1)
+ .setName("New Name")
+ .setLanguage("js")
+ .setParentKee("fghij")
+ .setDefault(false);
+
+ dao.update(dto);
+
+ checkTables("update", new String[] {"created_at", "updated_at", "rules_updated_at"}, "rules_profiles");
+ }
+
+ @Test
+ public void delete() {
+ setupData("shared");
+
+ dao.delete(1);
+
+ checkTables("delete", "rules_profiles");
+ }
+
+ @Test
+ public void find_all() {
+ setupData("shared");
+
+ DbSession session = getMyBatis().openSession(false);
+ try {
+ List<QualityProfileDto> dtos = dao.findAll(session);
+
+ assertThat(dtos).hasSize(2);
+
+ QualityProfileDto dto1 = dtos.get(0);
+ assertThat(dto1.getId()).isEqualTo(1);
+ assertThat(dto1.getName()).isEqualTo("Sonar Way");
+ assertThat(dto1.getLanguage()).isEqualTo("java");
+ assertThat(dto1.getParentKee()).isNull();
+
+ QualityProfileDto dto2 = dtos.get(1);
+ assertThat(dto2.getId()).isEqualTo(2);
+ assertThat(dto2.getName()).isEqualTo("Sonar Way");
+ assertThat(dto2.getLanguage()).isEqualTo("js");
+ assertThat(dto2.getParentKee()).isNull();
+ } finally {
+ session.close();
+ }
+ }
+
+ @Test
+ public void find_all_is_sorted_by_profile_name() {
+ setupData("select_all_is_sorted_by_profile_name");
+
+ List<QualityProfileDto> dtos = dao.findAll();
+
+ assertThat(dtos).hasSize(3);
+ assertThat(dtos.get(0).getName()).isEqualTo("First");
+ assertThat(dtos.get(1).getName()).isEqualTo("Second");
+ assertThat(dtos.get(2).getName()).isEqualTo("Third");
+ }
+
+ @Test
+ public void get_default_profile() {
+ setupData("shared");
+
+ QualityProfileDto java = dao.getDefaultProfile("java");
+ assertThat(java).isNotNull();
+ assertThat(java.getKey()).isEqualTo("java_sonar_way");
+
+ assertThat(dao.getDefaultProfile("js")).isNull();
+ }
+
+ @Test
+ public void get_by_name_and_language() {
+ setupData("shared");
+
+ QualityProfileDto dto = dao.getByNameAndLanguage("Sonar Way", "java");
+ assertThat(dto.getId()).isEqualTo(1);
+ assertThat(dto.getName()).isEqualTo("Sonar Way");
+ assertThat(dto.getLanguage()).isEqualTo("java");
+ assertThat(dto.getParentKee()).isNull();
+
+ assertThat(dao.getByNameAndLanguage("Sonar Way", "java")).isNotNull();
+ assertThat(dao.getByNameAndLanguage("Sonar Way", "unknown")).isNull();
+ }
+
+ @Test
+ public void find_by_language() {
+ setupData("select_by_language");
+
+ List<QualityProfileDto> result = dao.findByLanguage("java");
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("Sonar Way 1");
+ assertThat(result.get(1).getName()).isEqualTo("Sonar Way 2");
+ }
+
+ @Test
+ public void get_by_id() {
+ setupData("shared");
+
+ QualityProfileDto dto = dao.getById(1);
+ assertThat(dto.getId()).isEqualTo(1);
+ assertThat(dto.getName()).isEqualTo("Sonar Way");
+ assertThat(dto.getLanguage()).isEqualTo("java");
+ assertThat(dto.getParentKee()).isNull();
+
+ assertThat(dao.getById(555)).isNull();
+ }
+
+ @Test
+ public void get_parent_by_id() {
+ setupData("inheritance");
+
+ QualityProfileDto dto = dao.getParentById(1);
+ assertThat(dto.getId()).isEqualTo(3);
+ }
+
+ @Test
+ public void find_children() {
+ setupData("inheritance");
+
+ DbSession session = getMyBatis().openSession(false);
+ try {
+ List<QualityProfileDto> dtos = dao.findChildren(session, "java_parent");
+
+ assertThat(dtos).hasSize(2);
+
+ QualityProfileDto dto1 = dtos.get(0);
+ assertThat(dto1.getId()).isEqualTo(1);
+ assertThat(dto1.getName()).isEqualTo("Child1");
+ assertThat(dto1.getLanguage()).isEqualTo("java");
+ assertThat(dto1.getParentKee()).isEqualTo("java_parent");
+
+ QualityProfileDto dto2 = dtos.get(1);
+ assertThat(dto2.getId()).isEqualTo(2);
+ assertThat(dto2.getName()).isEqualTo("Child2");
+ assertThat(dto2.getLanguage()).isEqualTo("java");
+ assertThat(dto2.getParentKee()).isEqualTo("java_parent");
+
+ } finally {
+ session.close();
+ }
+ }
+
+ @Test
+ public void select_projects() {
+ setupData("projects");
+
+ assertThat(dao.selectProjects("Sonar Way", "java")).hasSize(2);
+ }
+
+ @Test
+ public void count_projects() {
+ setupData("projects");
+
+ assertThat(dao.countProjects("Sonar Way", "java")).isEqualTo(2);
+ }
+
+ @Test
+ public void count_projects_by_profile() {
+ setupData("projects");
+
+ assertThat(dao.countProjectsByProfileKey()).containsOnly(
+ MapEntry.entry("java_sonar_way", 2L),
+ MapEntry.entry("js_sonar_way", 2L));
+ }
+
+ @Test
+ public void select_by_project_id_and_language() {
+ setupData("projects");
+
+ QualityProfileDto dto = dao.getByProjectAndLanguage(1L, "java");
+ assertThat(dto.getId()).isEqualTo(1);
+ }
+
+ @Test
+ public void select_by_project_key_and_language() {
+ setupData("projects");
+
+ QualityProfileDto dto = dao.getByProjectAndLanguage("org.codehaus.sonar:sonar", "java", session);
+ assertThat(dto.getId()).isEqualTo(1);
+
+ assertThat(dao.getByProjectAndLanguage("org.codehaus.sonar:sonar", "unkown", session)).isNull();
+ assertThat(dao.getByProjectAndLanguage("unknown", "java", session)).isNull();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/rule/RuleDaoTest.java b/sonar-db/src/test/java/org/sonar/db/rule/RuleDaoTest.java
new file mode 100644
index 00000000000..34736c205ac
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/rule/RuleDaoTest.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.rule;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RuleDaoTest extends AbstractDaoTestCase {
+
+ private static RuleDao dao;
+
+ @Before
+ public void createDao() {
+ dao = new RuleDao(getMyBatis());
+ }
+
+ @Test
+ public void select_enables_and_non_manual() {
+ setupData("select_enables_and_non_manual");
+ List<RuleDto> ruleDtos = dao.selectEnablesAndNonManual();
+
+ assertThat(ruleDtos.size()).isEqualTo(1);
+ RuleDto ruleDto = ruleDtos.get(0);
+ assertThat(ruleDto.getId()).isEqualTo(1);
+ assertThat(ruleDto.getName()).isEqualTo("Avoid Null");
+ assertThat(ruleDto.getDescription()).isEqualTo("Should avoid NULL");
+ assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.HTML);
+ assertThat(ruleDto.getStatus()).isEqualTo(RuleStatus.READY);
+ assertThat(ruleDto.getRepositoryKey()).isEqualTo("checkstyle");
+ assertThat(ruleDto.getNoteData()).isEqualTo("Rule note with accents \u00e9\u00e8\u00e0");
+ assertThat(ruleDto.getSubCharacteristicId()).isEqualTo(100);
+ assertThat(ruleDto.getDefaultSubCharacteristicId()).isEqualTo(101);
+ assertThat(ruleDto.getRemediationFunction()).isEqualTo("LINEAR");
+ assertThat(ruleDto.getDefaultRemediationFunction()).isEqualTo("LINEAR_OFFSET");
+ assertThat(ruleDto.getRemediationCoefficient()).isEqualTo("1h");
+ assertThat(ruleDto.getDefaultRemediationCoefficient()).isEqualTo("5d");
+ assertThat(ruleDto.getRemediationOffset()).isEqualTo("5min");
+ assertThat(ruleDto.getDefaultRemediationOffset()).isEqualTo("10h");
+ assertThat(ruleDto.getEffortToFixDescription()).isEqualTo("squid.S115.effortToFix");
+ }
+
+ @Test
+ public void select_parameters() {
+ setupData("selectParameters");
+ List<RuleParamDto> ruleDtos = dao.selectParameters();
+
+ assertThat(ruleDtos.size()).isEqualTo(1);
+ RuleParamDto ruleDto = ruleDtos.get(0);
+ assertThat(ruleDto.getId()).isEqualTo(1);
+ assertThat(ruleDto.getName()).isEqualTo("myParameter");
+ assertThat(ruleDto.getDescription()).isEqualTo("My Parameter");
+ assertThat(ruleDto.getType()).isEqualTo("plop");
+ assertThat(ruleDto.getDefaultValue()).isEqualTo("plouf");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreDaoTest.java b/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreDaoTest.java
new file mode 100644
index 00000000000..30adc2b980e
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreDaoTest.java
@@ -0,0 +1,304 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.Semaphores;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.MyBatis;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SemaphoreDaoTest extends AbstractDaoTestCase {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+ private SemaphoreDao dao;
+ private System2 system;
+
+ @Before
+ public void before() {
+ system = System2.INSTANCE;
+ dao = new SemaphoreDao(getMyBatis(), system);
+ }
+
+ @Test
+ public void should_fail_to_acquire_if_null_semaphore_name() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Semaphore name must not be empty");
+
+ SemaphoreDao dao = new SemaphoreDao(getMyBatis(), system);
+ dao.acquire(null, 5000);
+ }
+
+ @Test
+ public void should_fail_to_acquire_if_blank_semaphore_name() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Semaphore name must not be empty");
+
+ SemaphoreDao dao = new SemaphoreDao(getMyBatis(), system);
+ dao.acquire("", 5000);
+ }
+
+ @Test
+ public void should_fail_to_acquire_if_negative_timeout() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Semaphore max age must be positive: -5000");
+
+ SemaphoreDao dao = new SemaphoreDao(getMyBatis(), system);
+ dao.acquire("foo", -5000);
+ }
+
+ @Test
+ public void should_fail_to_release_if_blank_semaphore_name() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Semaphore name must not be empty");
+
+ SemaphoreDao dao = new SemaphoreDao(getMyBatis(), system);
+ dao.release(null);
+ }
+
+ @Test
+ public void create_and_acquire_semaphore() {
+ Semaphores.Semaphore lock = dao.acquire("foo", 60);
+ assertThat(lock.isLocked()).isTrue();
+ assertThat(lock.getDurationSinceLocked()).isNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getLockedAt())).isTrue();
+
+ dao.release("foo");
+ assertThat(selectSemaphore("foo")).isNull();
+ }
+
+ @Test
+ public void create_and_acquire_and_update_semaphore() throws Exception {
+ Semaphores.Semaphore lock = dao.acquire("foo", 60);
+ assertThat(lock.isLocked()).isTrue();
+ assertThat(lock.getDurationSinceLocked()).isNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore.getCreatedAt()).isEqualTo(semaphore.getUpdatedAt());
+
+ Thread.sleep(1000);
+
+ dao.update(lock);
+
+ semaphore = selectSemaphore("foo");
+ assertThat(semaphore.getCreatedAt()).isLessThan(semaphore.getUpdatedAt());
+
+ dao.release("foo");
+ assertThat(selectSemaphore("foo")).isNull();
+ }
+
+ @Test
+ public void fail_to_update_null_semaphore() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Semaphore must not be null");
+
+ dao.update(null);
+ }
+
+ @Test
+ public void create_and_acquire_semaphore_when_maxage_is_zeo() {
+ Semaphores.Semaphore lock = dao.acquire("foo", 0);
+ assertThat(lock.isLocked()).isTrue();
+ assertThat(lock.getDurationSinceLocked()).isNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getLockedAt())).isTrue();
+
+ dao.release("foo");
+ assertThat(selectSemaphore("foo")).isNull();
+ }
+
+ @Test
+ public void create_and_acquire_semaphore_when_no_timeout() {
+ Semaphores.Semaphore lock = dao.acquire("foo");
+ assertThat(lock.isLocked()).isTrue();
+ assertThat(lock.getDurationSinceLocked()).isNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getLockedAt())).isTrue();
+
+ dao.release("foo");
+ assertThat(selectSemaphore("foo")).isNull();
+ }
+
+ @Test
+ public void fail_to_acquire_locked_semaphore() {
+ setupData("old_semaphore");
+ Semaphores.Semaphore lock = dao.acquire("foo", Integer.MAX_VALUE);
+ assertThat(lock.isLocked()).isFalse();
+ assertThat(lock.getDurationSinceLocked()).isNotNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isFalse();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isFalse();
+ assertThat(isRecent(semaphore.getLockedAt())).isFalse();
+ }
+
+ @Test
+ public void acquire_long_locked_semaphore() {
+ setupData("old_semaphore");
+ Semaphores.Semaphore lock = dao.acquire("foo", 60);
+ assertThat(lock.isLocked()).isTrue();
+ assertThat(lock.getDurationSinceLocked()).isNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isFalse();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getLockedAt())).isTrue();
+ }
+
+ @Test
+ public void acquire_locked_semaphore_when_timeout_is_zero() {
+ setupData("old_semaphore");
+ Semaphores.Semaphore lock = dao.acquire("foo", 0);
+ assertThat(lock.isLocked()).isTrue();
+ assertThat(lock.getDurationSinceLocked()).isNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isFalse();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isTrue();
+ assertThat(isRecent(semaphore.getLockedAt())).isTrue();
+
+ dao.release("foo");
+ assertThat(selectSemaphore("foo")).isNull();
+ }
+
+ @Test
+ public void fail_to_acquire_locked_semaphore_when_no_timeout() {
+ setupData("old_semaphore");
+ Semaphores.Semaphore lock = dao.acquire("foo");
+ assertThat(lock.isLocked()).isFalse();
+ assertThat(lock.getDurationSinceLocked()).isNotNull();
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(isRecent(semaphore.getCreatedAt())).isFalse();
+ assertThat(isRecent(semaphore.getUpdatedAt())).isFalse();
+ assertThat(isRecent(semaphore.getLockedAt())).isFalse();
+ }
+
+ @Test
+ public void should_select_semaphore_return_current_semaphore_when_acquiring() {
+ dao.acquire("foo");
+
+ SemaphoreDto semaphore = selectSemaphore("foo");
+ assertThat(semaphore).isNotNull();
+ assertThat(semaphore.getName()).isEqualTo("foo");
+ assertThat(semaphore.getCreatedAt()).isNotNull();
+ assertThat(semaphore.getUpdatedAt()).isNotNull();
+ assertThat(semaphore.getLockedAt()).isNotNull();
+ }
+
+ @Test
+ public void test_concurrent_locks() throws Exception {
+ for (int tests = 0; tests < 5; tests++) {
+ dao.release("my-lock");
+ int size = 5;
+ CyclicBarrier barrier = new CyclicBarrier(size);
+ CountDownLatch latch = new CountDownLatch(size);
+
+ AtomicInteger locks = new AtomicInteger(0);
+ for (int i = 0; i < size; i++) {
+ new Runner(dao, locks, barrier, latch).start();
+ }
+ latch.await();
+
+ // semaphore was locked only 1 time
+ assertThat(locks.get()).isEqualTo(1);
+ }
+ }
+
+ private SemaphoreDto selectSemaphore(String name) {
+ SqlSession session = getMyBatis().openSession();
+ try {
+ return dao.selectSemaphore(name, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ private boolean isRecent(Long date) {
+ int oneMinuteInMs = 60 * 1000;
+ long future = system.now() + oneMinuteInMs;
+ long past = system.now() - oneMinuteInMs;
+ return date > past && date < future;
+ }
+
+ private static class Runner extends Thread {
+ SemaphoreDao dao;
+ AtomicInteger locks;
+ CountDownLatch latch;
+ CyclicBarrier barrier;
+
+ Runner(SemaphoreDao dao, AtomicInteger atomicSeq, CyclicBarrier barrier, CountDownLatch latch) {
+ this.dao = dao;
+ this.locks = atomicSeq;
+ this.latch = latch;
+ this.barrier = barrier;
+ }
+
+ @Override
+ public void run() {
+ try {
+ barrier.await();
+ for (int i = 0; i < 100; i++) {
+ if (dao.acquire("my-lock", 60 * 60 * 24).isLocked()) {
+ locks.incrementAndGet();
+ }
+ }
+ latch.countDown();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreUpdaterTest.java b/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreUpdaterTest.java
new file mode 100644
index 00000000000..129a8914ce0
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoreUpdaterTest.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.Semaphores;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class SemaphoreUpdaterTest extends AbstractDaoTestCase {
+
+ private SemaphoreUpdater updater;
+ private SemaphoreDao dao;
+
+ @Before
+ public void before() {
+ dao = mock(SemaphoreDao.class);
+ updater = new SemaphoreUpdater(dao);
+ }
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testScheduleUpdate() throws Exception {
+ Semaphores.Semaphore semaphore = new Semaphores.Semaphore().setName("foo");
+ updater.scheduleForUpdate(semaphore, 1);
+
+ Thread.sleep(2000);
+
+ verify(dao, atLeastOnce()).update(semaphore);
+ }
+
+ @Test
+ public void testCancelUpdate() throws Exception {
+ Semaphores.Semaphore semaphore = new Semaphores.Semaphore().setName("foo");
+ updater.scheduleForUpdate(semaphore, 1);
+ updater.stopUpdate("foo");
+
+ Thread.sleep(2000);
+
+ verify(dao, never()).update(semaphore);
+ }
+
+ @Test
+ public void shouldNotFailWhenCancelNotExistingSemaphore() {
+ updater.stopUpdate("foo");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java b/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java
new file mode 100644
index 00000000000..a8d13641fdd
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.semaphore;
+
+import org.junit.Test;
+import org.sonar.api.utils.Semaphores;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SemaphoresImplTest {
+
+ @Test
+ public void should_be_a_bridge_over_dao() {
+ SemaphoreDao dao = mock(SemaphoreDao.class);
+ SemaphoreUpdater updater = mock(SemaphoreUpdater.class);
+ Semaphores.Semaphore semaphore = new Semaphores.Semaphore();
+ when(dao.acquire(anyString(), anyInt())).thenReturn(semaphore);
+
+ SemaphoresImpl impl = new SemaphoresImpl(dao, updater);
+
+ impl.acquire("do-xxx", 50000, 10);
+ verify(dao).acquire("do-xxx", 50000);
+
+ impl.acquire("do-xxx");
+ verify(dao).acquire("do-xxx");
+
+ impl.release("do-xxx");
+ verify(dao).release("do-xxx");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/source/FileSourceDtoTest.java b/sonar-db/src/test/java/org/sonar/db/source/FileSourceDtoTest.java
new file mode 100644
index 00000000000..624dd4262cb
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/source/FileSourceDtoTest.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.source;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.server.source.db.FileSourceDb;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class FileSourceDtoTest {
+
+ @Test
+ public void encode_and_decode_test_data() {
+ List<FileSourceDb.Test> tests = Arrays.asList(
+ FileSourceDb.Test.newBuilder()
+ .setName("name#1")
+ .build(),
+ FileSourceDb.Test.newBuilder()
+ .setName("name#2")
+ .build()
+ );
+
+ FileSourceDto sut = new FileSourceDto()
+ .setTestData(tests);
+
+ assertThat(sut.getTestData()).hasSize(2);
+ assertThat(sut.getTestData().get(0).getName()).isEqualTo("name#1");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/AuthorDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/AuthorDaoTest.java
new file mode 100644
index 00000000000..9cda23c3417
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/AuthorDaoTest.java
@@ -0,0 +1,129 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class AuthorDaoTest extends AbstractDaoTestCase {
+
+ private AuthorDao dao;
+
+ @Before
+ public void setUp() {
+ dao = new AuthorDao(getMyBatis(), new ResourceDao(getMyBatis(), System2.INSTANCE));
+ }
+
+ @Test
+ public void shouldSelectByLogin() {
+ setupData("shouldSelectByLogin");
+
+ AuthorDto authorDto = dao.selectByLogin("godin");
+ assertThat(authorDto.getId()).isEqualTo(1L);
+ assertThat(authorDto.getPersonId()).isEqualTo(13L);
+ assertThat(authorDto.getLogin()).isEqualTo("godin");
+
+ assertThat(dao.selectByLogin("simon")).isNull();
+ }
+
+ @Test
+ public void shouldInsertAuthor() {
+ setupData("shouldInsertAuthor");
+
+ dao.insertAuthor("godin", 13L);
+
+ checkTables("shouldInsertAuthor", new String[] {"created_at", "updated_at"}, "authors");
+ }
+
+ @Test
+ public void countDeveloperLogins() {
+ setupData("countDeveloperLogins");
+
+ assertThat(dao.countDeveloperLogins(1L)).isEqualTo(2);
+ assertThat(dao.countDeveloperLogins(98765L)).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldInsertAuthorAndDeveloper() {
+ setupData("shouldInsertAuthorAndDeveloper");
+
+ String login = "developer@company.net";
+ ResourceDto resourceDto = new ResourceDto().setName(login).setQualifier("DEV").setUuid("ABCD").setProjectUuid("ABCD").setModuleUuidPath(".");
+ dao.insertAuthorAndDeveloper(login, resourceDto);
+
+ checkTables("shouldInsertAuthorAndDeveloper",
+ new String[] {"created_at", "updated_at", "copy_resource_id", "description", "enabled", "kee", "deprecated_kee", "path", "language", "long_name", "person_id", "root_id",
+ "scope", "authorization_updated_at"},
+ "authors", "projects");
+ }
+
+ @Test
+ public void add_missing_module_uuid_path() {
+ setupData("add_missing_module_uuid_path");
+
+ dao.insertAuthorAndDeveloper("developer@company.net", new ResourceDto().setName("developer@company.net").setQualifier("DEV").setUuid("ABCD").setProjectUuid("ABCD")
+ .setModuleUuidPath(""));
+ dao.insertAuthorAndDeveloper("developer2@company.net", new ResourceDto().setName("developer2@company.net").setQualifier("DEV").setUuid("BCDE").setProjectUuid("BCDE"));
+
+ checkTables("add_missing_module_uuid_path",
+ new String[] {"created_at", "updated_at", "copy_resource_id", "description", "enabled", "kee", "deprecated_kee", "path", "language", "long_name", "person_id", "root_id",
+ "scope", "authorization_updated_at"},
+ "authors", "projects");
+ }
+
+ @Test
+ public void shouldPreventAuthorsDuplication() {
+ setupData("shouldPreventAuthorsDuplication");
+
+ try {
+ dao.insertAuthor("godin", 20L);
+ fail();
+ } catch (RuntimeException ex) {
+ }
+
+ checkTables("shouldPreventAuthorsDuplication", new String[] {"created_at", "updated_at"}, "authors");
+ }
+
+ @Test
+ public void shouldPreventAuthorsAndDevelopersDuplication() {
+ setupData("shouldPreventAuthorsAndDevelopersDuplication");
+
+ String login = "developer@company.net";
+ ResourceDto resourceDto = new ResourceDto().setName(login).setQualifier("DEV");
+
+ try {
+ dao.insertAuthorAndDeveloper("developer@company.net", resourceDto);
+ fail();
+ } catch (RuntimeException ex) {
+ }
+
+ checkTables("shouldPreventAuthorsAndDevelopersDuplication",
+ new String[] {"created_at", "updated_at", "copy_resource_id", "description", "enabled", "kee", "deprecated_kee", "path", "language", "long_name", "person_id", "root_id",
+ "scope", "authorization_updated_at"},
+ "authors", "projects");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/AuthorizationDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/AuthorizationDaoTest.java
new file mode 100644
index 00000000000..ce5990951f9
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/AuthorizationDaoTest.java
@@ -0,0 +1,295 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AuthorizationDaoTest extends AbstractDaoTestCase {
+
+ private static final int USER = 100;
+ private static final Long PROJECT_ID = 300L;
+ private static final Long PROJECT_ID_WITHOUT_SNAPSHOT = 400L;
+ private static final String PROJECT = "pj-w-snapshot";
+ private static final String PROJECT_WIHOUT_SNAPSHOT = "pj-wo-snapshot";
+
+ DbSession session;
+
+ AuthorizationDao authorization;
+
+ @Before
+ public void setUp() {
+ session = getMyBatis().openSession(false);
+ authorization = new AuthorizationDao(getMyBatis());
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void user_should_be_authorized() {
+ // but user is not in an authorized group
+ setupData("user_should_be_authorized");
+
+ Collection<Long> componentIds = authorization.keepAuthorizedProjectIds(session,
+ Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT),
+ USER, "user");
+
+ assertThat(componentIds).containsOnly(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT);
+
+ // user does not have the role "admin"
+ componentIds = authorization.keepAuthorizedProjectIds(session,
+ Sets.newHashSet(PROJECT_ID),
+ USER, "admin");
+ assertThat(componentIds).isEmpty();
+
+ assertThat(authorization.keepAuthorizedProjectIds(session,
+ Collections.<Long>emptySet(),
+ USER, "admin")).isEmpty();
+ }
+
+ @Test
+ public void keep_authorized_project_ids_for_user() {
+ setupData("keep_authorized_project_ids_for_user");
+
+ assertThat(authorization.keepAuthorizedProjectIds(session, Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT), USER, "user")).containsOnly(PROJECT_ID);
+
+ // user does not have the role "admin"
+ assertThat(authorization.keepAuthorizedProjectIds(session, Sets.newHashSet(PROJECT_ID), USER, "admin")).isEmpty();
+
+ // Empty list
+ assertThat(authorization.keepAuthorizedProjectIds(session, Collections.<Long>emptySet(), USER, "admin")).isEmpty();
+ }
+
+ @Test
+ public void keep_authorized_project_ids_for_group() {
+ setupData("keep_authorized_project_ids_for_group");
+
+ assertThat(authorization.keepAuthorizedProjectIds(session, Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT), USER, "user")).containsOnly(PROJECT_ID);
+
+ // user does not have the role "admin"
+ assertThat(authorization.keepAuthorizedProjectIds(session, Sets.newHashSet(PROJECT_ID), USER, "admin")).isEmpty();
+
+ // Empty list
+ assertThat(authorization.keepAuthorizedProjectIds(session, Collections.<Long>emptySet(), USER, "admin")).isEmpty();
+ }
+
+ @Test
+ public void keep_authorized_project_ids_for_anonymous() {
+ setupData("keep_authorized_project_ids_for_anonymous");
+
+ assertThat(authorization.keepAuthorizedProjectIds(session, Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT), null, "user")).containsOnly(PROJECT_ID);
+
+ // user does not have the role "admin"
+ assertThat(authorization.keepAuthorizedProjectIds(session, Sets.newHashSet(PROJECT_ID), null, "admin")).isEmpty();
+
+ // Empty list
+ assertThat(authorization.keepAuthorizedProjectIds(session, Collections.<Long>emptySet(), null, "admin")).isEmpty();
+ }
+
+ @Test
+ public void is_authorized_component_key_for_user() {
+ setupData("keep_authorized_project_ids_for_user");
+
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT, USER, "user")).isTrue();
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT_WIHOUT_SNAPSHOT, USER, "user")).isFalse();
+
+ // user does not have the role "admin"
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT, USER, "admin")).isFalse();
+ }
+
+ @Test
+ public void is_authorized_component_key_for_group() {
+ setupData("keep_authorized_project_ids_for_group");
+
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT, USER, "user")).isTrue();
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT_WIHOUT_SNAPSHOT, USER, "user")).isFalse();
+
+ // user does not have the role "admin"
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT, USER, "admin")).isFalse();
+ }
+
+ @Test
+ public void is_authorized_component_key_for_anonymous() {
+ setupData("keep_authorized_project_ids_for_anonymous");
+
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT, null, "user")).isTrue();
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT_WIHOUT_SNAPSHOT, null, "user")).isFalse();
+ assertThat(authorization.isAuthorizedComponentKey(PROJECT, null, "admin")).isFalse();
+ }
+
+ @Test
+ public void group_should_be_authorized() {
+ // user is in an authorized group
+ setupData("group_should_be_authorized");
+
+ Collection<Long> componentIds = authorization.keepAuthorizedProjectIds(session,
+ Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT),
+ USER, "user");
+
+ assertThat(componentIds).containsOnly(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT);
+
+ // group does not have the role "admin"
+ componentIds = authorization.keepAuthorizedProjectIds(session,
+ Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT),
+ USER, "admin");
+ assertThat(componentIds).isEmpty();
+ }
+
+ @Test
+ public void anonymous_should_be_authorized() {
+ setupData("anonymous_should_be_authorized");
+
+ Collection<Long> componentIds = authorization.keepAuthorizedProjectIds(session,
+ Sets.newHashSet(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT),
+ null, "user");
+
+ assertThat(componentIds).containsOnly(PROJECT_ID, PROJECT_ID_WITHOUT_SNAPSHOT);
+
+ // group does not have the role "admin"
+ componentIds = authorization.keepAuthorizedProjectIds(session,
+ Sets.newHashSet(PROJECT_ID),
+ null, "admin");
+ assertThat(componentIds).isEmpty();
+ }
+
+ @Test
+ public void should_return_root_project_keys_for_user() {
+ setupData("should_return_root_project_keys_for_user");
+
+ Collection<String> rootProjectIds = authorization.selectAuthorizedRootProjectsKeys(USER, "user");
+
+ assertThat(rootProjectIds).containsOnly(PROJECT);
+
+ // user does not have the role "admin"
+ rootProjectIds = authorization.selectAuthorizedRootProjectsKeys(USER, "admin");
+ assertThat(rootProjectIds).isEmpty();
+ }
+
+ @Test
+ public void should_return_root_project_keys_for_group() {
+ // but user is not in an authorized group
+ setupData("should_return_root_project_keys_for_group");
+
+ Collection<String> rootProjectIds = authorization.selectAuthorizedRootProjectsKeys(USER, "user");
+
+ assertThat(rootProjectIds).containsOnly(PROJECT);
+
+ // user does not have the role "admin"
+ rootProjectIds = authorization.selectAuthorizedRootProjectsKeys(USER, "admin");
+ assertThat(rootProjectIds).isEmpty();
+ }
+
+ @Test
+ public void should_return_root_project_keys_for_anonymous() {
+ setupData("should_return_root_project_keys_for_anonymous");
+
+ Collection<String> rootProjectIds = authorization.selectAuthorizedRootProjectsKeys(null, "user");
+
+ assertThat(rootProjectIds).containsOnly(PROJECT);
+
+ // group does not have the role "admin"
+ rootProjectIds = authorization.selectAuthorizedRootProjectsKeys(null, "admin");
+ assertThat(rootProjectIds).isEmpty();
+ }
+
+ @Test
+ public void should_return_root_project_uuids_for_user() {
+ setupData("should_return_root_project_keys_for_user");
+
+ Collection<String> rootProjectUuids = authorization.selectAuthorizedRootProjectsUuids(USER, "user");
+
+ assertThat(rootProjectUuids).containsOnly("ABCD");
+
+ // user does not have the role "admin"
+ rootProjectUuids = authorization.selectAuthorizedRootProjectsKeys(USER, "admin");
+ assertThat(rootProjectUuids).isEmpty();
+ }
+
+ @Test
+ public void should_return_root_project_uuids_for_group() {
+ // but user is not in an authorized group
+ setupData("should_return_root_project_keys_for_group");
+
+ Collection<String> rootProjectUuids = authorization.selectAuthorizedRootProjectsUuids(USER, "user");
+
+ assertThat(rootProjectUuids).containsOnly("ABCD");
+
+ // user does not have the role "admin"
+ rootProjectUuids = authorization.selectAuthorizedRootProjectsKeys(USER, "admin");
+ assertThat(rootProjectUuids).isEmpty();
+ }
+
+ @Test
+ public void should_return_root_project_uuids_for_anonymous() {
+ setupData("should_return_root_project_keys_for_anonymous");
+
+ Collection<String> rootProjectUuids = authorization.selectAuthorizedRootProjectsUuids(null, "user");
+
+ assertThat(rootProjectUuids).containsOnly("ABCD");
+
+ // group does not have the role "admin"
+ rootProjectUuids = authorization.selectAuthorizedRootProjectsKeys(null, "admin");
+ assertThat(rootProjectUuids).isEmpty();
+ }
+
+ @Test
+ public void should_return_user_global_permissions() {
+ setupData("should_return_user_global_permissions");
+
+ assertThat(authorization.selectGlobalPermissions("john")).containsOnly("user", "admin");
+ assertThat(authorization.selectGlobalPermissions("arthur")).containsOnly("user");
+ assertThat(authorization.selectGlobalPermissions("none")).isEmpty();
+ }
+
+ @Test
+ public void should_return_group_global_permissions() {
+ setupData("should_return_group_global_permissions");
+
+ assertThat(authorization.selectGlobalPermissions("john")).containsOnly("user", "admin");
+ assertThat(authorization.selectGlobalPermissions("arthur")).containsOnly("user");
+ assertThat(authorization.selectGlobalPermissions("none")).isEmpty();
+ }
+
+ @Test
+ public void should_return_global_permissions_for_anonymous() {
+ setupData("should_return_global_permissions_for_anonymous");
+
+ assertThat(authorization.selectGlobalPermissions(null)).containsOnly("user", "admin");
+ }
+
+ @Test
+ public void should_return_global_permissions_for_group_anyone() {
+ setupData("should_return_global_permissions_for_group_anyone");
+
+ assertThat(authorization.selectGlobalPermissions("anyone_user")).containsOnly("user", "profileadmin");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/GroupDtoTest.java b/sonar-db/src/test/java/org/sonar/db/user/GroupDtoTest.java
new file mode 100644
index 00000000000..35d79814cfc
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/GroupDtoTest.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupDtoTest {
+
+ @Test
+ public void getter_and_setter() {
+ GroupDto dto = new GroupDto()
+ .setId(1L)
+ .setName("sonar-users")
+ .setDescription("Sonar users");
+
+ assertThat(dto.getKey()).isEqualTo("sonar-users");
+ assertThat(dto.getName()).isEqualTo("sonar-users");
+ assertThat(dto.getId()).isEqualTo(1L);
+ assertThat(dto.getDescription()).isEqualTo("Sonar users");
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java
new file mode 100644
index 00000000000..f5327223d39
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java
@@ -0,0 +1,299 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import com.google.common.collect.Multimap;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.MyBatis;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
+@Category(DbTests.class)
+public class GroupMembershipDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ private GroupMembershipDao dao;
+
+ private DbSession dbSession;
+
+ @Before
+ public void setUp() {
+ dbTester.truncateTables();
+ dao = new GroupMembershipDao(dbTester.myBatis());
+ dbSession = dbTester.myBatis().openSession(false);
+ }
+
+ @After
+ public void tearDown() {
+ MyBatis.closeQuietly(dbSession);
+ }
+
+ @Test
+ public void select_all_groups_by_query() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ GroupMembershipQuery query = GroupMembershipQuery.builder().login("arthur").build();
+ List<GroupMembershipDto> result = dao.selectGroups(query, 200L);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void select_user_group() {
+ dbTester.prepareDbUnit(getClass(), "select_user_group.xml");
+
+ GroupMembershipQuery query = GroupMembershipQuery.builder().login("arthur").build();
+ List<GroupMembershipDto> result = dao.selectGroups(query, 201L);
+ assertThat(result).hasSize(1);
+
+ GroupMembershipDto dto = result.get(0);
+ assertThat(dto.getId()).isEqualTo(101L);
+ assertThat(dto.getName()).isEqualTo("sonar-users");
+ assertThat(dto.getDescription()).isEqualTo("Any new users created will automatically join this group");
+ assertThat(dto.getUserId()).isEqualTo(201L);
+ }
+
+ @Test
+ public void select_user_groups_by_query() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ // 200 is member of 3 groups
+ assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 200L)).hasSize(3);
+ // 201 is member of 1 group on 3
+ assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 201L)).hasSize(1);
+ // 999 is member of 0 group
+ assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 999L)).isEmpty();
+ }
+
+ @Test
+ public void select_groups_not_affected_to_a_user_by_query() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ // 200 is member of 3 groups
+ assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 200L)).isEmpty();
+ // 201 is member of 1 group on 3
+ assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 201L)).hasSize(2);
+ // 999 is member of 0 group
+ assertThat(dao.selectGroups(GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 2999L)).hasSize(3);
+ }
+
+ @Test
+ public void search_by_group_name() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").groupSearch("user").build(), 200L);
+ assertThat(result).hasSize(1);
+
+ assertThat(result.get(0).getName()).isEqualTo("sonar-users");
+
+ result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").groupSearch("sonar").build(), 200L);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void search_by_group_name_with_capitalization() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").groupSearch("UsER").build(), 200L);
+ assertThat(result).hasSize(1);
+
+ assertThat(result.get(0).getName()).isEqualTo("sonar-users");
+
+ result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").groupSearch("sonar").build(), 200L);
+ assertThat(result).hasSize(3);
+ }
+
+ @Test
+ public void should_be_sorted_by_group_name() {
+ dbTester.prepareDbUnit(getClass(), "should_be_sorted_by_group_name.xml");
+
+ List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").build(), 200L);
+ assertThat(result).hasSize(3);
+ assertThat(result.get(0).getName()).isEqualTo("sonar-administrators");
+ assertThat(result.get(1).getName()).isEqualTo("sonar-reviewers");
+ assertThat(result.get(2).getName()).isEqualTo("sonar-users");
+ }
+
+ @Test
+ public void should_be_paginated() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ List<GroupMembershipDto> result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").build(), 200L, 0, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("sonar-administrators");
+ assertThat(result.get(1).getName()).isEqualTo("sonar-reviewers");
+
+ result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").build(), 200L, 1, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("sonar-reviewers");
+ assertThat(result.get(1).getName()).isEqualTo("sonar-users");
+
+ result = dao.selectGroups(GroupMembershipQuery.builder().login("arthur").build(), 200L, 2, 1);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("sonar-users");
+ }
+
+ @Test
+ public void count_groups() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ // 200 is member of 3 groups
+ assertThat(dao.countGroups(dbSession, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 200L)).isEqualTo(3);
+ assertThat(dao.countGroups(dbSession, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 200L)).isZero();
+ // 201 is member of 1 group on 3
+ assertThat(dao.countGroups(dbSession, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 201L)).isEqualTo(1);
+ assertThat(dao.countGroups(dbSession, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 201L)).isEqualTo(2);
+ // 999 is member of 0 group
+ assertThat(dao.countGroups(dbSession, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.IN).build(), 999L)).isZero();
+ assertThat(dao.countGroups(dbSession, GroupMembershipQuery.builder().login("arthur").membership(GroupMembershipQuery.OUT).build(), 2999L)).isEqualTo(3);
+ }
+
+ @Test
+ public void count_users_by_group() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ assertThat(dao.countUsersByGroups(dbSession, Arrays.asList(100L, 101L, 102L, 103L))).containsOnly(
+ entry("sonar-users", 2), entry("sonar-reviewers", 1), entry("sonar-administrators", 1), entry("sonar-nobody", 0));
+ assertThat(dao.countUsersByGroups(dbSession, Arrays.asList(100L, 103L))).containsOnly(
+ entry("sonar-administrators", 1), entry("sonar-nobody", 0));
+ }
+
+ @Test
+ public void count_groups_by_login() {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+
+ assertThat(dao.selectGroupsByLogins(dbSession, Arrays.<String>asList()).keys()).isEmpty();
+ Multimap<String, String> groupsByLogin = dao.selectGroupsByLogins(dbSession, Arrays.asList("two-hundred", "two-hundred-one", "two-hundred-two"));
+ assertThat(groupsByLogin.get("two-hundred")).containsOnly("sonar-administrators", "sonar-users", "sonar-reviewers");
+ assertThat(groupsByLogin.get("two-hundred-one")).containsOnly("sonar-users");
+ assertThat(groupsByLogin.get("two-hundred-two")).isEmpty();
+ }
+
+ @Test
+ public void count_members() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ // 100 has 1 member and 1 non member
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.IN).build())).isEqualTo(1);
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.OUT).build())).isEqualTo(1);
+ // 101 has 2 members
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.IN).build())).isEqualTo(2);
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.OUT).build())).isZero();
+ // 102 has 1 member and 1 non member
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.IN).build())).isEqualTo(1);
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.OUT).build())).isEqualTo(1);
+ // 103 has no member
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.IN).build())).isZero();
+ assertThat(dao.countMembers(dbSession, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.OUT).build())).isEqualTo(2);
+ }
+
+ @Test
+ public void select_group_members_by_query() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ // 100 has 1 member
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(1);
+ // 101 has 2 members
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(2);
+ // 102 has 1 member
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(1);
+ // 103 has no member
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.IN).build(), 0, 10)).isEmpty();
+ }
+
+ @Test
+ public void select_users_not_affected_to_a_group_by_query() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ // 100 has 1 member
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(1);
+ // 101 has 2 members
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.OUT).build(), 0, 10)).isEmpty();
+ // 102 has 1 member
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(1);
+ // 103 has no member
+ assertThat(dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(2);
+ }
+
+ @Test
+ public void search_by_user_name_or_login() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ List<UserMembershipDto> result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).memberSearch("admin").build(), 0, 10);
+ assertThat(result).hasSize(2);
+
+ assertThat(result.get(0).getName()).isEqualTo("Admin");
+ assertThat(result.get(1).getName()).isEqualTo("Not Admin");
+
+ result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).memberSearch("not").build(), 0, 10);
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ public void search_by_login_or_name_with_capitalization() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ List<UserMembershipDto> result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).memberSearch("admin").build(), 0, 10);
+ assertThat(result).hasSize(2);
+
+ result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).memberSearch("AdMiN").build(), 0, 10);
+ assertThat(result).hasSize(2);
+
+ }
+
+ @Test
+ public void should_be_sorted_by_user_name() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ List<UserMembershipDto> result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).build(), 0, 10);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("Admin");
+ assertThat(result.get(1).getName()).isEqualTo("Not Admin");
+ }
+
+ @Test
+ public void members_should_be_paginated() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+
+ List<UserMembershipDto> result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).build(), 0, 2);
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getName()).isEqualTo("Admin");
+ assertThat(result.get(1).getName()).isEqualTo("Not Admin");
+
+ result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).build(), 1, 2);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getName()).isEqualTo("Not Admin");
+
+ result = dao.selectMembers(dbSession, UserMembershipQuery.builder().groupId(100L).build(), 2, 1);
+ assertThat(result).isEmpty();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDtoTest.java b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDtoTest.java
new file mode 100644
index 00000000000..c8a1dadb4c3
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipDtoTest.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import org.junit.Test;
+import org.sonar.core.user.GroupMembership;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupMembershipDtoTest {
+
+ @Test
+ public void to_group_with_permission_having_permission() {
+ GroupMembership group = new GroupMembershipDto()
+ .setId(1L)
+ .setName("users")
+ .setDescription("description")
+ .setUserId(10L)
+ .toGroupMembership();
+
+ assertThat(group.id()).isEqualTo(1);
+ assertThat(group.name()).isEqualTo("users");
+ assertThat(group.description()).isEqualTo("description");
+ assertThat(group.isMember()).isTrue();
+ }
+
+ @Test
+ public void to_group_with_permission_not_having_permission() {
+ GroupMembership group = new GroupMembershipDto()
+ .setId(1L)
+ .setName("users")
+ .setDescription("description")
+ .setUserId(null)
+ .toGroupMembership();
+
+ assertThat(group.id()).isEqualTo(1);
+ assertThat(group.name()).isEqualTo("users");
+ assertThat(group.description()).isEqualTo("description");
+ assertThat(group.isMember()).isFalse();
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipQueryTest.java b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipQueryTest.java
new file mode 100644
index 00000000000..3edbcf430ef
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipQueryTest.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class GroupMembershipQueryTest {
+
+ @Test
+ public void fail_on_null_login() {
+ GroupMembershipQuery.Builder builder = GroupMembershipQuery.builder();
+ builder.login(null);
+
+ try {
+ builder.build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(NullPointerException.class).hasMessage("User login cant be null.");
+ }
+ }
+
+ @Test
+ public void fail_on_invalid_membership() {
+ GroupMembershipQuery.Builder builder = GroupMembershipQuery.builder();
+ builder.login("nelson");
+ builder.membership("unknwown");
+
+ try {
+ builder.build();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Membership is not valid (got unknwown). Availables values are [ANY, IN, OUT]");
+ }
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipTest.java b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipTest.java
new file mode 100644
index 00000000000..9838d364417
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/GroupMembershipTest.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import org.junit.Test;
+import org.sonar.core.user.GroupMembership;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GroupMembershipTest {
+
+ @Test
+ public void test_setters_and_getters() throws Exception {
+ GroupMembership group = new GroupMembership()
+ .setId(1L)
+ .setName("users")
+ .setMember(true);
+
+ assertThat(group.id()).isEqualTo(1L);
+ assertThat(group.name()).isEqualTo("users");
+ assertThat(group.isMember()).isTrue();
+ }
+
+ @Test
+ public void test_equals() throws Exception {
+ assertThat(new GroupMembership().setName("users")).isEqualTo(new GroupMembership().setName("users"));
+ assertThat(new GroupMembership().setName("users")).isNotEqualTo(new GroupMembership().setName("reviewers"));
+
+ GroupMembership group = new GroupMembership()
+ .setId(1L)
+ .setName("users")
+ .setMember(true);
+ assertThat(group).isEqualTo(group);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/RoleDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/RoleDaoTest.java
new file mode 100644
index 00000000000..df2470f74f7
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/RoleDaoTest.java
@@ -0,0 +1,152 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.user;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class RoleDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ DbSession session;
+
+ RoleDao dao;
+
+ @Before
+ public void setUp() {
+ dbTester.truncateTables();
+ session = dbTester.myBatis().openSession(false);
+ dao = new RoleDao();
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void retrieve_global_user_permissions() {
+ dbTester.prepareDbUnit(getClass(), "globalUserPermissions.xml");
+
+ assertThat(dao.selectUserPermissions(session, "admin_user", null)).containsOnly(GlobalPermissions.SYSTEM_ADMIN, GlobalPermissions.QUALITY_PROFILE_ADMIN);
+ assertThat(dao.selectUserPermissions(session, "profile_admin_user", null)).containsOnly(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+ }
+
+ @Test
+ public void retrieve_resource_user_permissions() {
+ dbTester.prepareDbUnit(getClass(), "resourceUserPermissions.xml");
+
+ assertThat(dao.selectUserPermissions(session, "admin_user", 1L)).containsOnly(UserRole.ADMIN, UserRole.USER);
+ assertThat(dao.selectUserPermissions(session, "browse_admin_user", 1L)).containsOnly(UserRole.USER);
+ }
+
+ @Test
+ public void retrieve_global_group_permissions() {
+ dbTester.prepareDbUnit(getClass(), "globalGroupPermissions.xml");
+
+ assertThat(dao.selectGroupPermissions(session, "sonar-administrators", null)).containsOnly(GlobalPermissions.SYSTEM_ADMIN, GlobalPermissions.QUALITY_PROFILE_ADMIN,
+ GlobalPermissions.DASHBOARD_SHARING);
+ assertThat(dao.selectGroupPermissions(session, "sonar-users", null)).containsOnly(GlobalPermissions.DASHBOARD_SHARING);
+ assertThat(dao.selectGroupPermissions(session, DefaultGroups.ANYONE, null)).containsOnly(GlobalPermissions.PREVIEW_EXECUTION, GlobalPermissions.SCAN_EXECUTION);
+ assertThat(dao.selectGroupPermissions(session, "anyone", null)).containsOnly(GlobalPermissions.PREVIEW_EXECUTION, GlobalPermissions.SCAN_EXECUTION);
+ assertThat(dao.selectGroupPermissions(session, "AnYoNe", null)).containsOnly(GlobalPermissions.PREVIEW_EXECUTION, GlobalPermissions.SCAN_EXECUTION);
+ }
+
+ @Test
+ public void retrieve_resource_group_permissions() {
+ dbTester.prepareDbUnit(getClass(), "resourceGroupPermissions.xml");
+
+ assertThat(dao.selectGroupPermissions(session, "sonar-administrators", 1L)).containsOnly(UserRole.ADMIN, UserRole.CODEVIEWER);
+ assertThat(dao.selectGroupPermissions(session, "sonar-users", 1L)).containsOnly(UserRole.CODEVIEWER);
+ }
+
+ @Test
+ public void delete_global_user_permission() {
+ dbTester.prepareDbUnit(getClass(), "globalUserPermissions.xml");
+
+ UserRoleDto userRoleToDelete = new UserRoleDto().setUserId(200L).setRole(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+
+ dao.deleteUserRole(userRoleToDelete, session);
+ session.commit();
+
+ dbTester.assertDbUnit(getClass(), "globalUserPermissions-result.xml", "user_roles");
+ }
+
+ @Test
+ public void delete_resource_user_permission() {
+ dbTester.prepareDbUnit(getClass(), "resourceUserPermissions.xml");
+
+ UserRoleDto userRoleToDelete = new UserRoleDto().setUserId(200L).setRole(UserRole.USER).setResourceId(1L);
+
+ dao.deleteUserRole(userRoleToDelete, session);
+ session.commit();
+
+ dbTester.assertDbUnit(getClass(), "resourceUserPermissions-result.xml", "user_roles");
+ }
+
+ @Test
+ public void delete_global_group_permission() {
+ dbTester.prepareDbUnit(getClass(), "globalGroupPermissions.xml");
+
+ GroupRoleDto groupRoleToDelete = new GroupRoleDto().setGroupId(100L).setRole(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+
+ dao.deleteGroupRole(groupRoleToDelete, session);
+ session.commit();
+
+ dbTester.assertDbUnit(getClass(), "globalGroupPermissions-result.xml", "group_roles");
+ }
+
+ @Test
+ public void delete_resource_group_permission() {
+ dbTester.prepareDbUnit(getClass(), "resourceGroupPermissions.xml");
+
+ GroupRoleDto groupRoleToDelete = new GroupRoleDto().setGroupId(100L).setRole(UserRole.CODEVIEWER).setResourceId(1L);
+
+ dao.deleteGroupRole(groupRoleToDelete, session);
+ session.commit();
+
+ dbTester.assertDbUnit(getClass(), "resourceGroupPermissions-result.xml", "group_roles");
+ }
+
+ @Test
+ public void delete_all_group_permissions_by_group_id() {
+ dbTester.prepareDbUnit(getClass(), "deleteGroupPermissionsByGroupId.xml");
+
+ dao.deleteGroupRolesByGroupId(session, 100L);
+ session.commit();
+
+ dbTester.assertDbUnit(getClass(), "deleteGroupPermissionsByGroupId-result.xml", "group_roles");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/RoleMapperTest.java b/sonar-db/src/test/java/org/sonar/db/user/RoleMapperTest.java
new file mode 100644
index 00000000000..8c7f671c029
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/RoleMapperTest.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.MyBatis;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RoleMapperTest extends AbstractDaoTestCase {
+
+ private SqlSession session;
+
+ @Before
+ public void openSession() {
+ session = getMyBatis().openSession();
+ }
+
+ @After
+ public void closeSession() {
+ MyBatis.closeQuietly(session);
+ }
+
+ @Test
+ public void count_roles() {
+ setupData("countRoles");
+
+ RoleMapper mapper = session.getMapper(RoleMapper.class);
+ assertThat(mapper.countResourceGroupRoles(123L)).isEqualTo(2);
+ assertThat(mapper.countResourceUserRoles(123L)).isEqualTo(1);
+ }
+
+ @Test
+ public void delete_roles_by_resource_id() {
+ setupData("deleteRolesByResourceId");
+
+ RoleMapper mapper = session.getMapper(RoleMapper.class);
+ mapper.deleteGroupRolesByResourceId(123L);
+ mapper.deleteUserRolesByResourceId(123L);
+ session.commit();
+
+ checkTables("deleteRolesByResourceId", "group_roles", "user_roles");
+ }
+
+ @Test
+ public void insert_roles() {
+ setupData("insertRoles");
+
+ RoleMapper mapper = session.getMapper(RoleMapper.class);
+ mapper.insertGroupRole(new GroupRoleDto().setRole("admin").setGroupId(100L).setResourceId(123L));
+ mapper.insertGroupRole(new GroupRoleDto().setRole("user").setResourceId(123L));// Anyone
+ mapper.insertUserRole(new UserRoleDto().setRole("codeviewer").setUserId(200L).setResourceId(123L));// Anyone
+ session.commit();
+
+ checkTables("insertRoles", "group_roles", "user_roles");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
new file mode 100644
index 00000000000..01536c72643
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
@@ -0,0 +1,271 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.user.UserQuery;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.AbstractDaoTestCase;
+import org.sonar.db.DbSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class UserDaoTest extends AbstractDaoTestCase {
+
+ UserDao dao;
+
+ System2 system2;
+
+ DbSession session;
+
+ @Before
+ public void setUp() {
+ session = getMyBatis().openSession(false);
+ system2 = mock(System2.class);
+ dao = new UserDao(getMyBatis(), system2);
+ }
+
+ @After
+ public void tearDown() {
+ session.close();
+ }
+
+ @Test
+ public void selectUserByLogin_ignore_inactive() {
+ setupData("selectActiveUserByLogin");
+
+ UserDto user = dao.getUser(50);
+ assertThat(user.getLogin()).isEqualTo("inactive_user");
+
+ user = dao.selectActiveUserByLogin("inactive_user");
+ assertThat(user).isNull();
+ }
+
+ @Test
+ public void selectUserByLogin_not_found() {
+ setupData("selectActiveUserByLogin");
+
+ UserDto user = dao.selectActiveUserByLogin("not_found");
+ assertThat(user).isNull();
+ }
+
+ @Test
+ public void selectUsersByLogins() {
+ setupData("selectUsersByLogins");
+
+ Collection<UserDto> users = dao.selectUsersByLogins(Arrays.asList("marius", "inactive_user", "other"));
+ assertThat(users).hasSize(2);
+ assertThat(users).extracting("login").containsOnly("marius", "inactive_user");
+ }
+
+ @Test
+ public void selectUsersByLogins_empty_logins() {
+ // no need to access db
+ Collection<UserDto> users = dao.selectUsersByLogins(Collections.<String>emptyList());
+ assertThat(users).isEmpty();
+ }
+
+ @Test
+ public void selectUsersByQuery_all() {
+ setupData("selectUsersByQuery");
+
+ UserQuery query = UserQuery.builder().includeDeactivated().build();
+ List<UserDto> users = dao.selectUsers(query);
+ assertThat(users).hasSize(2);
+ }
+
+ @Test
+ public void selectUsersByQuery_only_actives() {
+ setupData("selectUsersByQuery");
+
+ UserQuery query = UserQuery.ALL_ACTIVES;
+ List<UserDto> users = dao.selectUsers(query);
+ assertThat(users).hasSize(1);
+ assertThat(users.get(0).getName()).isEqualTo("Marius");
+ }
+
+ @Test
+ public void selectUsersByQuery_filter_by_login() {
+ setupData("selectUsersByQuery");
+
+ UserQuery query = UserQuery.builder().logins("marius", "john").build();
+ List<UserDto> users = dao.selectUsers(query);
+ assertThat(users).hasSize(1);
+ assertThat(users.get(0).getName()).isEqualTo("Marius");
+ }
+
+ @Test
+ public void selectUsersByQuery_search_by_login_text() {
+ setupData("selectUsersByText");
+
+ UserQuery query = UserQuery.builder().searchText("sbr").build();
+ List<UserDto> users = dao.selectUsers(query);
+ assertThat(users).hasSize(1);
+ assertThat(users.get(0).getLogin()).isEqualTo("sbrandhof");
+ }
+
+ @Test
+ public void selectUsersByQuery_search_by_name_text() {
+ setupData("selectUsersByText");
+
+ UserQuery query = UserQuery.builder().searchText("Simon").build();
+ List<UserDto> users = dao.selectUsers(query);
+ assertThat(users).hasSize(1);
+ assertThat(users.get(0).getLogin()).isEqualTo("sbrandhof");
+ }
+
+ @Test
+ public void selectUsersByQuery_escape_special_characters_in_like() {
+ setupData("selectUsersByText");
+
+ UserQuery query = UserQuery.builder().searchText("%s%").build();
+ // we expect really a login or name containing the 3 characters "%s%"
+
+ List<UserDto> users = dao.selectUsers(query);
+ assertThat(users).isEmpty();
+ }
+
+ @Test
+ public void selectGroupByName() {
+ setupData("selectGroupByName");
+
+ GroupDto group = dao.selectGroupByName("sonar-users");
+ assertThat(group).isNotNull();
+ assertThat(group.getId()).isEqualTo(1L);
+ assertThat(group.getName()).isEqualTo("sonar-users");
+ assertThat(group.getDescription()).isEqualTo("Sonar Users");
+ assertThat(group.getCreatedAt()).isNotNull();
+ assertThat(group.getUpdatedAt()).isNotNull();
+ }
+
+ @Test
+ public void selectGroupByName_not_found() {
+ setupData("selectGroupByName");
+
+ GroupDto group = dao.selectGroupByName("not-found");
+ assertThat(group).isNull();
+ }
+
+ @Test
+ public void insert_user() {
+ Long date = DateUtils.parseDate("2014-06-20").getTime();
+
+ UserDto userDto = new UserDto()
+ .setId(1L)
+ .setLogin("john")
+ .setName("John")
+ .setEmail("jo@hn.com")
+ .setScmAccounts(",jo.hn,john2,")
+ .setActive(true)
+ .setSalt("1234")
+ .setCryptedPassword("abcd")
+ .setCreatedAt(date)
+ .setUpdatedAt(date);
+ dao.insert(session, userDto);
+ session.commit();
+
+ UserDto user = dao.selectActiveUserByLogin("john");
+ assertThat(user).isNotNull();
+ assertThat(user.getId()).isNotNull();
+ assertThat(user.getLogin()).isEqualTo("john");
+ assertThat(user.getName()).isEqualTo("John");
+ assertThat(user.getEmail()).isEqualTo("jo@hn.com");
+ assertThat(user.isActive()).isTrue();
+ assertThat(user.getScmAccounts()).isEqualTo(",jo.hn,john2,");
+ assertThat(user.getSalt()).isEqualTo("1234");
+ assertThat(user.getCryptedPassword()).isEqualTo("abcd");
+ assertThat(user.getCreatedAt()).isEqualTo(date);
+ assertThat(user.getUpdatedAt()).isEqualTo(date);
+ }
+
+ @Test
+ public void update_user() {
+ setupData("update_user");
+
+ Long date = DateUtils.parseDate("2014-06-21").getTime();
+
+ UserDto userDto = new UserDto()
+ .setId(1L)
+ .setLogin("john")
+ .setName("John Doo")
+ .setEmail("jodoo@hn.com")
+ .setScmAccounts(",jo.hn,john2,johndoo,")
+ .setActive(false)
+ .setSalt("12345")
+ .setCryptedPassword("abcde")
+ .setUpdatedAt(date);
+ dao.update(session, userDto);
+ session.commit();
+
+ UserDto user = dao.getUser(1);
+ assertThat(user).isNotNull();
+ assertThat(user.getId()).isEqualTo(1L);
+ assertThat(user.getLogin()).isEqualTo("john");
+ assertThat(user.getName()).isEqualTo("John Doo");
+ assertThat(user.getEmail()).isEqualTo("jodoo@hn.com");
+ assertThat(user.isActive()).isFalse();
+ assertThat(user.getScmAccounts()).isEqualTo(",jo.hn,john2,johndoo,");
+ assertThat(user.getSalt()).isEqualTo("12345");
+ assertThat(user.getCryptedPassword()).isEqualTo("abcde");
+ assertThat(user.getCreatedAt()).isEqualTo(1418215735482L);
+ assertThat(user.getUpdatedAt()).isEqualTo(date);
+ }
+
+ @Test
+ public void deactivate_user() {
+ setupData("deactivate_user");
+
+ when(system2.now()).thenReturn(1500000000000L);
+
+ String login = "marius";
+ boolean deactivated = dao.deactivateUserByLogin(login);
+ assertThat(deactivated).isTrue();
+
+ assertThat(dao.selectActiveUserByLogin(login)).isNull();
+
+ UserDto userDto = dao.getUser(100);
+ assertThat(userDto.isActive()).isFalse();
+ assertThat(userDto.getUpdatedAt()).isEqualTo(1500000000000L);
+
+ checkTables("deactivate_user",
+ "dashboards", "active_dashboards", "groups_users", "issue_filters",
+ "issue_filter_favourites", "measure_filters", "measure_filter_favourites",
+ "properties", "user_roles");
+ }
+
+ @Test
+ public void deactivate_missing_user() {
+ setupData("deactivate_user");
+
+ String login = "does_not_exist";
+ boolean deactivated = dao.deactivateUserByLogin(login);
+ assertThat(deactivated).isFalse();
+ assertThat(dao.selectActiveUserByLogin(login)).isNull();
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserDtoTest.java b/sonar-db/src/test/java/org/sonar/db/user/UserDtoTest.java
new file mode 100644
index 00000000000..74e21d9fe94
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/user/UserDtoTest.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserDtoTest {
+
+ @Test
+ public void encode_scm_accounts() {
+ assertThat(UserDto.encodeScmAccounts(null)).isNull();
+ assertThat(UserDto.encodeScmAccounts(Collections.<String>emptyList())).isNull();
+ assertThat(UserDto.encodeScmAccounts(Arrays.asList("foo"))).isEqualTo("\nfoo\n");
+ assertThat(UserDto.encodeScmAccounts(Arrays.asList("foo", "bar"))).isEqualTo("\nfoo\nbar\n");
+ }
+
+ @Test
+ public void decode_scm_accounts() {
+ assertThat(UserDto.decodeScmAccounts(null)).isEmpty();
+ assertThat(UserDto.decodeScmAccounts("\nfoo\n")).containsOnly("foo");
+ assertThat(UserDto.decodeScmAccounts("\nfoo\nbar\n")).containsOnly("foo", "bar");
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/DatabaseVersionTest.java b/sonar-db/src/test/java/org/sonar/db/version/DatabaseVersionTest.java
new file mode 100644
index 00000000000..05f8f056bf2
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/version/DatabaseVersionTest.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import org.junit.Test;
+import org.sonar.db.AbstractDaoTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DatabaseVersionTest extends AbstractDaoTestCase {
+ @Test
+ public void getVersion() {
+ setupData("getVersion");
+
+ Integer version = new DatabaseVersion(getMyBatis()).getVersion();
+
+ assertThat(version).isEqualTo(123);
+ }
+
+ @Test
+ public void getVersion_no_rows() {
+ setupData("getVersion_no_rows");
+
+ Integer version = new DatabaseVersion(getMyBatis()).getVersion();
+
+ assertThat(version).isNull();
+ }
+
+ @Test
+ public void getStatus() {
+ assertThat(DatabaseVersion.getStatus(null, 150)).isEqualTo(DatabaseVersion.Status.FRESH_INSTALL);
+ assertThat(DatabaseVersion.getStatus(123, 150)).isEqualTo(DatabaseVersion.Status.REQUIRES_UPGRADE);
+ assertThat(DatabaseVersion.getStatus(150, 150)).isEqualTo(DatabaseVersion.Status.UP_TO_DATE);
+ assertThat(DatabaseVersion.getStatus(200, 150)).isEqualTo(DatabaseVersion.Status.REQUIRES_DOWNGRADE);
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v44/ChangeLogTest.java b/sonar-db/src/test/java/org/sonar/db/version/v44/ChangeLogTest.java
new file mode 100644
index 00000000000..b260b793146
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/version/v44/ChangeLogTest.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v44;
+
+import org.junit.Test;
+
+import static com.google.code.beanmatchers.BeanMatchers.hasValidBeanConstructor;
+import static com.google.code.beanmatchers.BeanMatchers.hasValidGettersAndSetters;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+
+public class ChangeLogTest {
+
+ @Test
+ public void test_bean() throws Exception {
+ assertThat(ChangeLog.class, allOf(
+ hasValidBeanConstructor(),
+ hasValidGettersAndSetters()));
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v44/ProfileMeasureTest.java b/sonar-db/src/test/java/org/sonar/db/version/v44/ProfileMeasureTest.java
new file mode 100644
index 00000000000..050408aa4cd
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/version/v44/ProfileMeasureTest.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version.v44;
+
+import org.junit.Test;
+
+import static com.google.code.beanmatchers.BeanMatchers.hasValidBeanConstructor;
+import static com.google.code.beanmatchers.BeanMatchers.hasValidGettersAndSetters;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+
+public class ProfileMeasureTest {
+
+ @Test
+ public void test_bean() throws Exception {
+ assertThat(ProfileMeasure.class, allOf(
+ hasValidBeanConstructor(),
+ hasValidGettersAndSetters()));
+ }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v50/ComponentTest.java b/sonar-db/src/test/java/org/sonar/db/version/v50/ComponentTest.java
new file mode 100644
index 00000000000..409d2b1b35b
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/version/v50/ComponentTest.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.db.version.v50;
+
+import org.junit.Test;
+
+import static com.google.code.beanmatchers.BeanMatchers.hasValidGettersAndSetters;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+
+public class ComponentTest {
+
+ @Test
+ public void test_bean() throws Exception {
+ assertThat(Component.class, allOf(
+ hasValidGettersAndSetters()));
+ }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java b/sonar-db/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java
new file mode 100644
index 00000000000..d4743f578cc
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java
@@ -0,0 +1,229 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.jpa.test;
+
+import java.io.InputStream;
+import java.sql.SQLException;
+import org.apache.commons.io.IOUtils;
+import org.apache.ibatis.session.SqlSession;
+import org.dbunit.Assertion;
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.database.DatabaseConfig;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.CompositeDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.ReplacementDataSet;
+import org.dbunit.dataset.filter.DefaultColumnFilter;
+import org.dbunit.dataset.xml.FlatXmlDataSet;
+import org.dbunit.ext.mssql.InsertIdentityOperation;
+import org.dbunit.operation.DatabaseOperation;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseCommands;
+import org.sonar.db.H2Database;
+import org.sonar.db.MyBatis;
+import org.sonar.db.deprecated.NullQueue;
+import org.sonar.db.version.DatabaseVersion;
+import org.sonar.db.version.SchemaMigrationMapper;
+
+import static org.junit.Assert.fail;
+
+/**
+ * @deprecated this class does not support non-H2 databases
+ */
+@Deprecated
+public abstract class AbstractDbUnitTestCase {
+ private static Database database;
+ private static MyBatis myBatis;
+ private static DatabaseCommands databaseCommands;
+ private IDatabaseTester databaseTester;
+
+ @BeforeClass
+ public static void startDatabase() throws SQLException {
+ if (database == null) {
+ database = new H2Database("sonarHibernate", true);
+ database.start();
+
+ databaseCommands = DatabaseCommands.forDialect(database.getDialect());
+
+ myBatis = new MyBatis(database, new NullQueue());
+ myBatis.start();
+ try (SqlSession session = myBatis.openSession(false)) {
+ session.getMapper(SchemaMigrationMapper.class).insert(String.valueOf(DatabaseVersion.LAST_VERSION));
+ session.commit();
+ }
+ }
+ }
+
+ @Before
+ public void startDbUnit() throws Exception {
+ databaseCommands.truncateDatabase(database.getDataSource());
+ databaseTester = new DataSourceDatabaseTester(database.getDataSource());
+ }
+
+ protected MyBatis getMyBatis() {
+ return myBatis;
+ }
+
+ protected Database getDatabase() {
+ return database;
+ }
+
+ protected void setupData(String... testNames) {
+ InputStream[] streams = new InputStream[testNames.length];
+ try {
+ for (int i = 0; i < testNames.length; i++) {
+ String className = getClass().getName();
+ className = String.format("/%s/%s.xml", className.replace(".", "/"), testNames[i]);
+ streams[i] = getClass().getResourceAsStream(className);
+ if (streams[i] == null) {
+ throw new RuntimeException("Test not found :" + className);
+ }
+ }
+
+ setupData(streams);
+ databaseCommands.resetPrimaryKeys(database.getDataSource());
+ } catch (SQLException e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ for (InputStream stream : streams) {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+ }
+
+ private void setupData(InputStream... dataSetStream) {
+ IDatabaseConnection connection = null;
+ try {
+ IDataSet[] dataSets = new IDataSet[dataSetStream.length];
+ for (int i = 0; i < dataSetStream.length; i++) {
+ dataSets[i] = getData(dataSetStream[i]);
+ }
+ databaseTester.setDataSet(new CompositeDataSet(dataSets));
+
+ connection = createConnection();
+
+ new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, databaseTester.getDataSet());
+ } catch (Exception e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ private void closeQuietly(IDatabaseConnection connection) {
+ try {
+ if (connection != null) {
+ connection.close();
+ }
+ } catch (SQLException ignored) {
+
+ }
+ }
+
+ protected void checkTables(String testName, String... tables) {
+ checkTables(testName, new String[0], tables);
+ }
+
+ protected void checkTables(String testName, String[] excludedColumnNames, String... tables) {
+ IDatabaseConnection connection = null;
+ try {
+ connection = createConnection();
+
+ IDataSet dataSet = connection.createDataSet();
+ IDataSet expectedDataSet = getExpectedData(testName);
+ for (String table : tables) {
+ ITable filteredTable = DefaultColumnFilter.excludedColumnsTable(dataSet.getTable(table), excludedColumnNames);
+ ITable filteredExpectedTable = DefaultColumnFilter.excludedColumnsTable(expectedDataSet.getTable(table), excludedColumnNames);
+ Assertion.assertEquals(filteredExpectedTable, filteredTable);
+ }
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ } catch (SQLException e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ protected void checkTable(String testName, String table, String... columns) {
+ IDatabaseConnection connection = null;
+ try {
+ connection = createConnection();
+
+ IDataSet dataSet = connection.createDataSet();
+ IDataSet expectedDataSet = getExpectedData(testName);
+ ITable filteredTable = DefaultColumnFilter.includedColumnsTable(dataSet.getTable(table), columns);
+ ITable filteredExpectedTable = DefaultColumnFilter.includedColumnsTable(expectedDataSet.getTable(table), columns);
+ Assertion.assertEquals(filteredExpectedTable, filteredTable);
+
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ } catch (SQLException e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ private IDatabaseConnection createConnection() {
+ try {
+ IDatabaseConnection connection = databaseTester.getConnection();
+ connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, databaseCommands.getDbUnitFactory());
+ return connection;
+ } catch (Exception e) {
+ throw translateException("Error while getting connection", e);
+ }
+ }
+
+ private IDataSet getExpectedData(String testName) {
+ String className = getClass().getName();
+ className = String.format("/%s/%s-result.xml", className.replace('.', '/'), testName);
+
+ InputStream in = getClass().getResourceAsStream(className);
+ try {
+ return getData(in);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ private IDataSet getData(InputStream stream) {
+ try {
+ ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream));
+ dataSet.addReplacementObject("[null]", null);
+ dataSet.addReplacementObject("[false]", Boolean.FALSE);
+ dataSet.addReplacementObject("[true]", Boolean.TRUE);
+ return dataSet;
+ } catch (Exception e) {
+ throw translateException("Could not read the dataset stream", e);
+ }
+ }
+
+ private static RuntimeException translateException(String msg, Exception cause) {
+ RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage()));
+ runtimeException.setStackTrace(cause.getStackTrace());
+ return runtimeException;
+ }
+}
diff --git a/sonar-db/src/test/resources/logback-test.xml b/sonar-db/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..4b0ff6da04b
--- /dev/null
+++ b/sonar-db/src/test/resources/logback-test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration>
+
+ <appender name="STDOUT"
+ class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>
+ %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
+ </pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.dbunit">
+ <level value="WARN"/>
+ </logger>
+
+ <!-- set to level DEBUG to log SQL requests executed by MyBatis -->
+ <logger name="java.sql">
+ <level value="WARN"/>
+ </logger>
+
+ <!-- required for DryRunDatabaseFactoryTest -->
+ <logger name="org.elasticsearch">
+ <level value="WARN"/>
+ </logger>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration>
diff --git a/sonar-db/src/test/resources/org/sonar/api/database/configuration/DatabaseConfigurationTest/some-properties.xml b/sonar-db/src/test/resources/org/sonar/api/database/configuration/DatabaseConfigurationTest/some-properties.xml
new file mode 100644
index 00000000000..64c38fb55ea
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/api/database/configuration/DatabaseConfigurationTest/some-properties.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <properties prop_key="key1" resource_id="[null]" text_value="value1" user_id="[null]"/>
+ <properties prop_key="key2" resource_id="[null]" text_value="value2" user_id="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/core/qualitymodel/DefaultModelFinderTest/shared.xml b/sonar-db/src/test/resources/org/sonar/core/qualitymodel/DefaultModelFinderTest/shared.xml
new file mode 100644
index 00000000000..6e9cab1c815
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/qualitymodel/DefaultModelFinderTest/shared.xml
@@ -0,0 +1,13 @@
+<dataset>
+ <quality_models id="1" name="M1"/>
+ <quality_models id="2" name="M2"/>
+
+ <characteristics id="1" kee="M1C1" name="M1C1" parent_id="[null]" root_id="[null]" rule_id="[null]"
+ characteristic_order="1" enabled="true"/>
+ <characteristics id="2" kee="M1C2" name="M1C2" parent_id="[null]" root_id="[null]" ule_id="[null]"
+ characteristic_order="1" enabled="true"/>
+ <characteristics id="3" kee="M2C1" name="M2C1" parent_id="[null]" root_id="[null]" rule_id="[null]"
+ characteristic_order="1" enabled="true"/>
+
+ <characteristic_edges child_id="2" parent_id="1"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/fail_when_no_default_template_is_defined.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/fail_when_no_default_template_is_defined.xml
new file mode 100644
index 00000000000..1fffc1facb2
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/fail_when_no_default_template_is_defined.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <projects id="123" kee="project"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles-result.xml
new file mode 100644
index 00000000000..5318ae07093
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles-result.xml
@@ -0,0 +1,33 @@
+<dataset>
+ <projects id="123" kee="com.foo:bar"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!--
+ new rows : sonar-administrators (admin), sonar-users (user & codeviewer), Anyone (user & codeviewer),
+ -->
+ <group_roles id="3" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="4" group_id="101" resource_id="123" role="user"/>
+ <group_roles id="5" group_id="[null]" resource_id="123" role="user"/>
+ <group_roles id="6" group_id="101" resource_id="123" role="codeviewer"/>
+ <group_roles id="7" group_id="[null]" resource_id="123" role="codeviewer"/>
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default_template_20130101_010203"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="101" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="user"/>
+ <perm_templates_groups id="4" template_id="1" group_id="101" permission_reference="codeviewer"/>
+ <perm_templates_groups id="5" template_id="1" group_id="[null]" permission_reference="codeviewer"/>
+
+ <perm_templates_users/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles.xml
new file mode 100644
index 00000000000..1291c08b42a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles.xml
@@ -0,0 +1,26 @@
+<dataset>
+
+ <projects id="123" kee="com.foo:bar"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default_template_20130101_010203"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="101" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="user"/>
+ <perm_templates_groups id="4" template_id="1" group_id="101" permission_reference="codeviewer"/>
+ <perm_templates_groups id="5" template_id="1" group_id="[null]" permission_reference="codeviewer"/>
+
+ <perm_templates_users/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern-result.xml
new file mode 100644
index 00000000000..9ada7f379cb
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern-result.xml
@@ -0,0 +1,35 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <groups id="102" name="sonar-foos"/>
+ <groups id="103" name="sonar-bars"/>
+
+ <users id="200" login="foo" name="Foo" email="[null]" active="[true]"/>
+ <users id="201" login="bar" name="Bar" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!--
+ new rows
+ -->
+ <group_roles id="3" group_id="102" resource_id="123" role="user"/>
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default" key_pattern="[null]"/>
+
+ <permission_templates id="2" name="Start with foo" kee="foo_tmpl" key_pattern="foo\..*"/>
+ <permission_templates id="3" name="Start with bar" kee="bar_tmpl" key_pattern="bar\..*"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+
+ <perm_templates_groups id="2" template_id="2" group_id="102" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="3" group_id="103" permission_reference="user"/>
+
+ <perm_templates_users/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern.xml
new file mode 100644
index 00000000000..667ef671ce1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesPattern.xml
@@ -0,0 +1,31 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <groups id="102" name="sonar-foos"/>
+ <groups id="103" name="sonar-bars"/>
+
+ <users id="200" login="foo" name="Foo" email="[null]" active="[true]"/>
+ <users id="201" login="bar" name="Bar" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default" key_pattern="[null]"/>
+
+ <permission_templates id="2" name="Start with foo" kee="foo_tmpl" key_pattern="foo\..*"/>
+ <permission_templates id="3" name="Start with bar" kee="bar_tmpl" key_pattern="bar\..*"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+
+ <perm_templates_groups id="2" template_id="2" group_id="102" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="3" group_id="103" permission_reference="user"/>
+
+ <perm_templates_users/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject-result.xml
new file mode 100644
index 00000000000..a70196dc547
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject-result.xml
@@ -0,0 +1,44 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- created permissions should be based on the TRK template -->
+ <group_roles id="3" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="4" group_id="101" resource_id="123" role="admin"/>
+ <group_roles id="5" group_id="100" resource_id="123" role="user"/>
+ <group_roles id="6" group_id="100" resource_id="123" role="codeviewer"/>
+ <group_roles id="7" group_id="[null]" resource_id="123" role="codeviewer"/>
+
+ <user_roles id="2" user_id="200" resource_id="123" role="codeviewer"/>
+
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="101" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="user"/>
+ <perm_templates_groups id="4" template_id="1" group_id="101" permission_reference="codeviewer"/>
+ <perm_templates_groups id="5" template_id="1" group_id="[null]" permission_reference="codeviewer"/>
+
+
+ <!-- default permission template for TRK -->
+ <permission_templates id="2" name="default_for_TRK"/>
+
+ <perm_templates_groups id="6" template_id="2" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="7" template_id="2" group_id="101" permission_reference="admin"/>
+ <perm_templates_groups id="8" template_id="2" group_id="100" permission_reference="user"/>
+ <perm_templates_groups id="9" template_id="2" group_id="100" permission_reference="codeviewer"/>
+ <perm_templates_groups id="10" template_id="2" group_id="[null]" permission_reference="codeviewer"/>
+
+ <perm_templates_users id="1" template_id="2" user_id="200" permission_reference="codeviewer"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject.xml
new file mode 100644
index 00000000000..3b60c1c78d4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesProject.xml
@@ -0,0 +1,35 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default_20130101_010203"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="101" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="user"/>
+ <perm_templates_groups id="4" template_id="1" group_id="101" permission_reference="codeviewer"/>
+ <perm_templates_groups id="5" template_id="1" group_id="[null]" permission_reference="codeviewer"/>
+
+
+ <!-- default permission template for TRK -->
+ <permission_templates id="2" name="default_for_TRK" kee="default_for_trk_20130101_010203"/>
+
+ <perm_templates_groups id="6" template_id="2" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="7" template_id="2" group_id="101" permission_reference="admin"/>
+ <perm_templates_groups id="8" template_id="2" group_id="100" permission_reference="user"/>
+ <perm_templates_groups id="9" template_id="2" group_id="100" permission_reference="codeviewer"/>
+ <perm_templates_groups id="10" template_id="2" group_id="[null]" permission_reference="codeviewer"/>
+
+ <perm_templates_users id="1" template_id="2" user_id="200" permission_reference="codeviewer"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesSeveralPattern.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesSeveralPattern.xml
new file mode 100644
index 00000000000..25fdc99252c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRolesSeveralPattern.xml
@@ -0,0 +1,31 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <groups id="102" name="sonar-foos"/>
+ <groups id="103" name="sonar-bars"/>
+
+ <users id="200" login="foo" name="Foo" email="[null]" active="[true]"/>
+ <users id="201" login="bar" name="Bar" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default" key_pattern="[null]"/>
+
+ <permission_templates id="2" name="Start with foo" kee="foo_tmpl" key_pattern="foo\..*"/>
+ <permission_templates id="3" name="Start with foo again" kee="foo2_tmpl" key_pattern="foo.*"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+
+ <perm_templates_groups id="2" template_id="2" group_id="102" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="3" group_id="103" permission_reference="user"/>
+
+ <perm_templates_users/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group-result.xml
new file mode 100644
index 00000000000..ea9b6e062fc
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group-result.xml
@@ -0,0 +1,20 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- new rows : sonar-administrators (admin) -->
+ <group_roles id="3" group_id="100" resource_id="123" role="admin"/>
+
+ <!-- default permission template -->
+ <permission_templates id="1" name="default_template" kee="default_template_20130101_010203"/>
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group.xml
new file mode 100644
index 00000000000..d1b61aa180c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_unknown_group.xml
@@ -0,0 +1,19 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- default permission template for TRK -->
+ <permission_templates id="1" name="default_template" kee="default_template_20130101_010203"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="999" permission_reference="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users-result.xml
new file mode 100644
index 00000000000..319040d44b5
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users-result.xml
@@ -0,0 +1,23 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+ <users id="201" login="disabled" name="Disabled" email="[null]" active="[false]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- new row : marius (admin) -->
+ <user_roles id="2" user_id="200" resource_id="123" role="admin"/>
+
+ <!-- default permission template for TRK -->
+ <permission_templates id="1" name="default_for_TRK" kee="default_for_trk_20130101_010203"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="200" permission_reference="admin"/>
+ <perm_templates_users id="2" template_id="1" user_id="201" permission_reference="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users.xml
new file mode 100644
index 00000000000..72a71613609
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantDefaultRoles_users.xml
@@ -0,0 +1,20 @@
+<dataset>
+ <projects id="123" kee="foo.project"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+ <users id="201" login="disabled" name="Disabled" email="[null]" active="[false]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- default permission template for TRK -->
+ <permission_templates id="1" name="default_for_TRK" kee="default_for_trk_20130101_010203"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="200" permission_reference="admin"/>
+ <perm_templates_users id="2" template_id="1" user_id="201" permission_reference="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole-result.xml
new file mode 100644
index 00000000000..9d3ae472eb6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles group_id="100" resource_id="123" role="admin"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole.xml
new file mode 100644
index 00000000000..8e0c38ff39f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone-result.xml
new file mode 100644
index 00000000000..62ecece0705
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles id="1" group_id="[null]" resource_id="123" role="admin"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone.xml
new file mode 100644
index 00000000000..8e0c38ff39f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_anyone.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found-result.xml
new file mode 100644
index 00000000000..41ed8af1f82
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <!-- already existed -->
+ <group_roles id="1" group_id="[null]" resource_id="123" role="admin"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found.xml
new file mode 100644
index 00000000000..41ed8af1f82
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_group_not_found.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <!-- already existed -->
+ <group_roles id="1" group_id="[null]" resource_id="123" role="admin"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted-result.xml
new file mode 100644
index 00000000000..41ed8af1f82
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <!-- already existed -->
+ <group_roles id="1" group_id="[null]" resource_id="123" role="admin"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted.xml
new file mode 100644
index 00000000000..41ed8af1f82
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantGroupRole_ignore_if_not_persisted.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <!-- already existed -->
+ <group_roles id="1" group_id="[null]" resource_id="123" role="admin"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole-result.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole-result.xml
new file mode 100644
index 00000000000..7f154cd1915
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <user_roles user_id="200" resource_id="123" role="admin"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole.xml
new file mode 100644
index 00000000000..24349836265
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/grantUserRole.xml
@@ -0,0 +1,3 @@
+<dataset>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/hasRoles.xml b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/hasRoles.xml
new file mode 100644
index 00000000000..3d5f9a5ecf2
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/resource/DefaultResourcePermissionsTest/hasRoles.xml
@@ -0,0 +1,16 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- only_users -->
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- only_groups -->
+ <group_roles id="1" group_id="100" resource_id="2" role="admin"/>
+
+ <!-- groups_and_users -->
+ <group_roles id="2" group_id="101" resource_id="3" role="user"/>
+ <user_roles id="2" user_id="200" resource_id="3" role="admin"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/core/user/DeprecatedUserFinderTest/fixture.xml b/sonar-db/src/test/resources/org/sonar/core/user/DeprecatedUserFinderTest/fixture.xml
new file mode 100644
index 00000000000..9d370e86a70
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/core/user/DeprecatedUserFinderTest/fixture.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <users id="1" login="simon" name="Simon Brandhof" email="simon.brandhof@sonarsource.com"/>
+ <users id="2" login="godin" name="Evgeny Mandrikov" email="evgeny.mandrikov@sonarsource.com"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture-including-ghost-projects-and-technical-project.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture-including-ghost-projects-and-technical-project.xml
new file mode 100644
index 00000000000..01781afd684
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture-including-ghost-projects-and-technical-project.xml
@@ -0,0 +1,134 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+ <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path=""/>
+ <snapshots id="10" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228136280000" build_date="1228136280000"
+ version="[null]" path=""/>
+
+ <!-- project -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ uuid="EFGH" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD."
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+ <snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1."/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts:org.struts"
+ name="org.struts" root_id="1"
+ description="[null]"
+ uuid="GHIJ" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+ <snapshots id="3" project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="DIR" qualifier="PAC" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1.2."/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts:org.struts.RequestContext"
+ name="RequestContext" root_id="1"
+ uuid="KLMN" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="CLA" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1.2.3."/>
+
+ <!-- technical project -->
+ <projects id="5" root_id="[null]" scope="PRJ" qualifier="TRK" kee="COPYorg.struts:struts" name="Struts"
+ uuid="TECHPROJECT" project_uuid="[null]" module_uuid="[null]" module_uuid_path="."
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="1" person_id="[null]" authorization_updated_at="[null]"/>
+
+ <!-- project without snapshot status=P-->
+ <projects id="6" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.shindig" name="Shinding"
+ uuid="ONLYERRORS" project_uuid="[null]" module_uuid="[null]" module_uuid_path="."
+ description="the description" long_name="Shinding"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+ <snapshots id="6" project_id="6" parent_snapshot_id="[null]" root_project_id="6" root_snapshot_id="[null]"
+ status="U" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path=""/>
+ <snapshots id="7" project_id="6" parent_snapshot_id="6" root_project_id="6" root_snapshot_id="6"
+ status="U" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228309080000" build_date="1228309080000"
+ version="[null]" path=""/>
+
+
+ <!-- project without snapshot -->
+ <projects id="7" root_id="[null]" kee="org.sample:sample" name="Sample"
+ scope="PRJ" qualifier="TRK" long_name="Sample"
+ uuid="NOSNAPSHOT" project_uuid="[null]" module_uuid="[null]" module_uuid_path="."
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- project not enabled -->
+ <projects id="8" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache:tika" name="Tika"
+ description="the description" long_name="Tika"
+ uuid="DISABLED" project_uuid="[null]" module_uuid="[null]" module_uuid_path="."
+ enabled="[false]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+ <snapshots id="8" project_id="8" parent_snapshot_id="[null]" root_project_id="8" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path=""/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture.xml
new file mode 100644
index 00000000000..76692345910
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/fixture.xml
@@ -0,0 +1,86 @@
+<dataset>
+
+ <!-- Struts projects is authorized for all user -->
+ <group_roles id="1" group_id="[null]" resource_id="1" role="user"/>
+
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ created_at="2008-12-02" authorization_updated_at="123456789"/>
+ <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path=""/>
+ <snapshots id="10" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228136280000" build_date="1228136280000"
+ version="[null]" path=""/>
+
+ <!-- module -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
+ uuid="BCDE" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="2008-12-02" authorization_updated_at="[null]"/>
+ <snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1."/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts"
+ uuid="CDEF" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE."
+ name="src/org/struts" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts"
+ created_at="2008-12-02" authorization_updated_at="[null]"/>
+ <snapshots id="3" project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="DIR" qualifier="PAC" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1.2."/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="FIL"
+ kee="org.struts:struts-core:src/org/struts/RequestContext.java"
+ uuid="DEFG" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE."
+ name="RequestContext.java" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ path="src/org/struts/RequestContext.java"
+ created_at="2008-12-02" authorization_updated_at="[null]"/>
+
+ <snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="CLA" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1.2.3."/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/getResources_exclude_disabled.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/getResources_exclude_disabled.xml
new file mode 100644
index 00000000000..f1bf15b6b7c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/getResources_exclude_disabled.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <!-- disabled -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ uuid="DISABLED" project_uuid="[null]" module_uuid="[null]" module_uuid_path="."
+ enabled="[false]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- enabled -->
+ <projects id="2" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ uuid="ENABLED" project_uuid="[null]" module_uuid="[null]" module_uuid_path="."
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/get_last_snapshot_by_component_uuid.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/get_last_snapshot_by_component_uuid.xml
new file mode 100644
index 00000000000..9304e9363b6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/get_last_snapshot_by_component_uuid.xml
@@ -0,0 +1,85 @@
+<dataset>
+
+ <!-- Struts projects is authorized for all user -->
+ <group_roles id="1" group_id="[null]" resource_id="1" role="user"/>
+
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="[null]"/>
+ <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="previous_analysis" period1_param="[null]" period1_date="[null]"
+ period2_mode="days" period2_param="30" period2_date="1316815200000"
+ period3_mode="days" period3_param="90" period3_date="1311631200000"
+ period4_mode="previous_analysis" period4_param="[null]" period4_date="[null]"
+ period5_mode="previous_version" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path=""/>
+ <snapshots id="10" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228136280000" build_date="1228136280000"
+ version="[null]" path=""/>
+
+ <!-- module -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
+ uuid="EFGH" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+ <snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1."/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts"
+ uuid="GHIJ" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
+ name="src/org/struts" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts"
+ authorization_updated_at="[null]"/>
+ <snapshots id="3" project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="DIR" qualifier="PAC" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1.2."/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="FIL"
+ kee="org.struts:struts-core:src/org/struts/RequestContext.java"
+ uuid="KLMN" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
+ name="RequestContext.java" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ path="src/org/struts/RequestContext.java" authorization_updated_at="[null]"/>
+
+ <snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="CLA" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="1.2.3."/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert-result.xml
new file mode 100644
index 00000000000..1c3af66e6fd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert-result.xml
@@ -0,0 +1,17 @@
+<dataset>
+
+ <projects id="1" root_id="[null]" uuid="ABCD" project_uuid="EFGH" module_uuid="EFGH" module_uuid_path=".EFGH."
+ scope="FIL" qualifier="FIL" kee="org.struts:struts:/src/main/java/org/struts/Action.java" name="Action"
+ description="[null]" long_name="org.struts.Action"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[ignore]"
+ path="/foo/bar" deprecated_kee="org.struts:struts:org.struts.Action"
+ authorization_updated_at="123456789"/>
+
+ <projects id="2" root_id="[null]" uuid="BCDE" project_uuid="FGHI" module_uuid="FGHI" module_uuid_path=".FGHI."
+ scope="FIL" qualifier="FIL" kee="org.struts:struts:/src/main/java/org/struts/Filter.java" name="Filter"
+ description="[null]" long_name="org.struts.Filter"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[ignore]"
+ path="[null]" deprecated_kee="org.struts:struts:org.struts.Filter"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/insert.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update-result.xml
new file mode 100644
index 00000000000..34afce983b9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update-result.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <projects id="1" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="MVC Framework" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="/foo/bar" deprecated_kee="deprecated key"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update.xml
new file mode 100644
index 00000000000..69e73cdb6df
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <projects id="1" root_id="200" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ scope="PRJ" qualifier="TRK" kee="old key" name="old name"
+ description="old name" long_name="old long name"
+ enabled="[false]" language="old" copy_resource_id="2" person_id="3" created_at="[null]" path="/old/foo/bar"
+ deprecated_kee="old deprecated key"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date-result.xml
new file mode 100644
index 00000000000..a6abf2fab1b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date-result.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <projects id="1" root_id="200" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ scope="PRJ" qualifier="TRK" kee="old key" name="old name"
+ description="old name" long_name="old long name"
+ enabled="[false]" language="old" copy_resource_id="2" person_id="3" created_at="[null]" path="/old/foo/bar"
+ deprecated_kee="old deprecated key"
+ authorization_updated_at="987654321"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date.xml
new file mode 100644
index 00000000000..69e73cdb6df
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceDaoTest/update_authorization_date.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <projects id="1" root_id="200" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ scope="PRJ" qualifier="TRK" kee="old key" name="old name"
+ description="old name" long_name="old long name"
+ enabled="[false]" language="old" copy_resource_id="2" person_id="3" created_at="[null]" path="/old/foo/bar"
+ deprecated_kee="old deprecated key"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject-result.xml
new file mode 100644
index 00000000000..48899d945c3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject-result.xml
@@ -0,0 +1,69 @@
+<dataset>
+
+ <!-- project "struts" -> module "struts-core" -> package org.struts -> file "RequestContext" -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects long_name="[null]" id="2" scope="PRJ" qualifier="BRC" kee="org.struts:struts-core" name="Struts Core"
+ uuid="BCDE" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- note that the root_id of package/file is wrong. It references the module but not the root project -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts-core:org.struts"
+ uuid="CDEF" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE"
+ name="org.struts" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects long_name="org.struts.RequestContext" id="4" scope="CLA" qualifier="CLA"
+ uuid="DEFG" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE"
+ kee="org.struts:struts-core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1"/>
+ <snapshots purge_status="[null]" id="2" islast="[true]" root_project_id="1" project_id="2"/>
+ <snapshots purge_status="[null]" id="3" islast="[true]" root_project_id="1" project_id="3"/>
+ <snapshots purge_status="[null]" id="4" islast="[true]" root_project_id="1" project_id="4"/>
+
+ <!-- The major goal is to test root_project_id -->
+
+ <!-- RequestContext -->
+ <resource_index kee="requestcontext" position="0" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="equestcontext" position="1" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="questcontext" position="2" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="uestcontext" position="3" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="estcontext" position="4" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="stcontext" position="5" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="tcontext" position="6" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="context" position="7" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="ontext" position="8" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="ntext" position="9" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="text" position="10" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="ext" position="11" name_size="14" resource_id="4" root_project_id="1" qualifier="CLA"/>
+
+ <!-- Struts -->
+ <resource_index kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+
+ <!-- Struts Core -->
+ <resource_index kee="struts core" position="0" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="truts core" position="1" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="ruts core" position="2" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="uts core" position="3" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="ts core" position="4" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="s core" position="5" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee=" core" position="6" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="core" position="7" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+ <resource_index kee="ore" position="8" name_size="11" resource_id="2" root_project_id="1" qualifier="BRC"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject.xml
new file mode 100644
index 00000000000..a1cd4ef6f5a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexMultiModulesProject.xml
@@ -0,0 +1,34 @@
+<dataset>
+
+ <!-- project "struts" -> module "struts-core" -> package org.struts -> file "RequestContext" -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects long_name="[null]" id="2" scope="PRJ" qualifier="BRC" kee="org.struts:struts-core" name="Struts Core"
+ uuid="BCDE" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- note that the root_id of package/file is wrong. It references the module but not the root project -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts-core:org.struts"
+ uuid="CDEF" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE"
+ name="org.struts" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ uuid="DEFG" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE"
+ kee="org.struts:struts-core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1"/>
+ <snapshots purge_status="[null]" id="2" islast="[true]" root_project_id="1" project_id="2"/>
+ <snapshots purge_status="[null]" id="3" islast="[true]" root_project_id="1" project_id="3"/>
+ <snapshots purge_status="[null]" id="4" islast="[true]" root_project_id="1" project_id="4"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects-result.xml
new file mode 100644
index 00000000000..ee9ab480a7c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects-result.xml
@@ -0,0 +1,52 @@
+<dataset>
+
+ <!-- project -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="2" scope="DIR" qualifier="PAC" kee="org.struts:struts:org.struts"
+ uuid="BCDE" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ name="org.struts" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="3" scope="CLA" qualifier="CLA"
+ uuid="CDEF" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ kee="org.struts:struts:org.struts.RequestContext"
+ name="RequestContext" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+ <snapshots purge_status="[null]" id="2" islast="[true]" root_project_id="1" project_id="2" scope="DIR"
+ qualifier="PAC"/>
+ <snapshots purge_status="[null]" id="3" islast="[true]" root_project_id="1" project_id="3" scope="CLA"
+ qualifier="CLA"/>
+
+ <!-- RequestContext -->
+ <resource_index kee="requestcontext" position="0" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="equestcontext" position="1" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="questcontext" position="2" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="uestcontext" position="3" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="estcontext" position="4" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="stcontext" position="5" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="tcontext" position="6" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="context" position="7" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="ontext" position="8" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="ntext" position="9" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="text" position="10" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+ <resource_index kee="ext" position="11" name_size="14" resource_id="3" root_project_id="1" qualifier="CLA"/>
+
+ <!-- Struts -->
+ <resource_index kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects.xml
new file mode 100644
index 00000000000..0043b4ced0b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexProjects.xml
@@ -0,0 +1,31 @@
+<dataset>
+
+ <!-- project -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="2" scope="DIR" qualifier="PAC" kee="org.struts:struts:org.struts"
+ uuid="BCDE" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ name="org.struts" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="3" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts:org.struts.RequestContext"
+ uuid="CDEF" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ name="RequestContext" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+ <snapshots purge_status="[null]" id="2" islast="[true]" root_project_id="1" project_id="2" scope="DIR"
+ qualifier="PAC"/>
+ <snapshots purge_status="[null]" id="3" islast="[true]" root_project_id="1" project_id="3" scope="FIL"
+ qualifier="CLA"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource-result.xml
new file mode 100644
index 00000000000..f4cf0805ede
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+ <resource_index kee="ziputils" position="0" name_size="8" resource_id="10" root_project_id="8" qualifier="FIL"/>
+ <resource_index kee="iputils" position="1" name_size="8" resource_id="10" root_project_id="8" qualifier="FIL"/>
+ <resource_index kee="putils" position="2" name_size="8" resource_id="10" root_project_id="8" qualifier="FIL"/>
+ <resource_index kee="utils" position="3" name_size="8" resource_id="10" root_project_id="8" qualifier="FIL"/>
+ <resource_index kee="tils" position="4" name_size="8" resource_id="10" root_project_id="8" qualifier="FIL"/>
+ <resource_index kee="ils" position="5" name_size="8" resource_id="10" root_project_id="8" qualifier="FIL"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource.xml
new file mode 100644
index 00000000000..5a4a28b7df3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexResource.xml
@@ -0,0 +1 @@
+<dataset></dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource-result.xml
new file mode 100644
index 00000000000..c9941e492bd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource-result.xml
@@ -0,0 +1,3 @@
+<dataset>
+ <resource_index kee="ab" position="0" name_size="2" resource_id="10" root_project_id="3" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource.xml
new file mode 100644
index 00000000000..5a4a28b7df3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldIndexTwoLettersLongResource.xml
@@ -0,0 +1 @@
+<dataset></dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotIndexPackages.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotIndexPackages.xml
new file mode 100644
index 00000000000..0043b4ced0b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotIndexPackages.xml
@@ -0,0 +1,31 @@
+<dataset>
+
+ <!-- project -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="2" scope="DIR" qualifier="PAC" kee="org.struts:struts:org.struts"
+ uuid="BCDE" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ name="org.struts" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="3" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts:org.struts.RequestContext"
+ uuid="CDEF" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path=".ABCD."
+ name="RequestContext" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+ <snapshots purge_status="[null]" id="2" islast="[true]" root_project_id="1" project_id="2" scope="DIR"
+ qualifier="PAC"/>
+ <snapshots purge_status="[null]" id="3" islast="[true]" root_project_id="1" project_id="3" scope="FIL"
+ qualifier="CLA"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource-result.xml
new file mode 100644
index 00000000000..4292af5708d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <resource_index kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource.xml
new file mode 100644
index 00000000000..89a3a8c465d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldNotReindexUnchangedResource.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <resource_index id="1" kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="2" kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="3" kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="4" kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource-result.xml
new file mode 100644
index 00000000000..52db35f3ad4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource-result.xml
@@ -0,0 +1,3 @@
+<dataset>
+ <resource_index kee="as" position="0" name_size="2" resource_id="1" root_project_id="1" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource.xml
new file mode 100644
index 00000000000..af5863edbda
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexNewTwoLettersLongResource.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="AS"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+
+ <!-- the index is on the old name "ST" but not on "AS" -->
+ <resource_index id="1" kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="2" kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="3" kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="4" kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource-result.xml
new file mode 100644
index 00000000000..52db35f3ad4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource-result.xml
@@ -0,0 +1,3 @@
+<dataset>
+ <resource_index kee="as" position="0" name_size="2" resource_id="1" root_project_id="1" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource.xml
new file mode 100644
index 00000000000..529e83c1a44
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReIndexTwoLettersLongResource.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="AS"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+
+ <!-- the index is on the old name "ST" but not on "AS" -->
+ <resource_index id="1" kee="st" position="0" name_size="2" resource_id="1" root_project_id="1" qualifier="TRK"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming-result.xml
new file mode 100644
index 00000000000..e28b5945816
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming-result.xml
@@ -0,0 +1,24 @@
+<dataset>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Apache Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+
+ <resource_index kee="apache struts" position="0" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="pache struts" position="1" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ache struts" position="2" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="che struts" position="3" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="he struts" position="4" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="e struts" position="5" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee=" struts" position="6" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="struts" position="7" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="truts" position="8" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ruts" position="9" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="uts" position="10" name_size="13" resource_id="1" root_project_id="1" qualifier="TRK"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming.xml
new file mode 100644
index 00000000000..84815e52e64
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexProjectAfterRenaming.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Apache Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots purge_status="[null]" id="1" islast="[true]" root_project_id="1" project_id="1" scope="PRJ"
+ qualifier="TRK"/>
+
+ <!-- the index is on the old name "Struts" but not on "Apache Struts -->
+ <resource_index id="1" kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="2" kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="3" kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="4" kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource-result.xml
new file mode 100644
index 00000000000..b9f39157aea
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource-result.xml
@@ -0,0 +1,10 @@
+<dataset>
+ <resource_index kee="new struts" position="0" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ew struts" position="1" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="w struts" position="2" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee=" struts" position="3" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="struts" position="4" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="truts" position="5" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="ruts" position="6" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="uts" position="7" name_size="10" resource_id="1" root_project_id="1" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource.xml
new file mode 100644
index 00000000000..89a3a8c465d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceIndexerDaoTest/shouldReindexResource.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <resource_index id="1" kee="struts" position="0" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="2" kee="truts" position="1" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="3" kee="ruts" position="2" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index id="4" kee="uts" position="3" name_size="6" resource_id="1" root_project_id="1" qualifier="TRK"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shared.xml
new file mode 100644
index 00000000000..400243b4d19
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shared.xml
@@ -0,0 +1,72 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts" uuid="A"
+ project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ description="[null]" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core" uuid="B" project_uuid="A"
+ module_uuid="[null]" module_uuid_path=".A."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.struts:struts-core"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:/src/org/struts"
+ name="org.struts" root_id="2" uuid="C" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-core:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts-core:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="2" uuid="D" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-core:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.struts:struts-ui" name="Struts UI" uuid="E" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".E."
+ scope="PRJ" qualifier="BRC" long_name="Struts UI"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.struts:struts-ui"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="DIR" kee="org.struts:struts-ui:/src/org/struts"
+ name="org.struts" root_id="5" uuid="F" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-ui:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts-ui:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="5" uuid="G" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-ui:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core" uuid="H" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".H."
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="foo:struts-core"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml
new file mode 100644
index 00000000000..962995f42b6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml
@@ -0,0 +1,73 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.struts:struts" name="Struts" uuid="A"
+ project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ description="[null]" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.apache.struts:struts-core" name="Struts Core" uuid="B" project_uuid="A"
+ module_uuid="[null]" module_uuid_path=".A."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.apache.struts:struts-core"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR"
+ kee="org.apache.struts:struts-core:/src/org/struts"
+ name="org.struts" root_id="2" uuid="C" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts-core:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="org.apache.struts:struts-core:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="2" uuid="D" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts-core:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.apache.struts:struts-ui" name="Struts UI" uuid="E" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".E."
+ scope="PRJ" qualifier="BRC" long_name="Struts UI"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.apache.struts:struts-ui"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="DIR" kee="org.apache.struts:struts-ui:/src/org/struts"
+ name="org.struts" root_id="5" uuid="F" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts-ui:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA"
+ kee="org.apache.struts:struts-ui:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="5" uuid="G" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts-ui:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core" uuid="H" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".H."
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="foo:struts-core"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml
new file mode 100644
index 00000000000..81e4290832e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml
@@ -0,0 +1,72 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts" uuid="A"
+ project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ description="[null]" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core" uuid="B" project_uuid="A"
+ module_uuid="[null]" module_uuid_path=".A."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.struts:struts-core"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:/src/org/struts"
+ name="org.struts" root_id="2" uuid="C" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-core:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts-core:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="2" uuid="D" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-core:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.struts:struts-web" name="Struts UI" uuid="E" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".E."
+ scope="PRJ" qualifier="BRC" long_name="Struts UI"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.struts:struts-web"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="DIR" kee="org.struts:struts-web:/src/org/struts"
+ name="org.struts" root_id="5" uuid="F" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-web:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts-web:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="5" uuid="G" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-web:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core" uuid="H" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".H."
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="foo:struts-core"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml
new file mode 100644
index 00000000000..83686f2ba90
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml
@@ -0,0 +1,64 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.struts:struts" name="Struts" uuid="A"
+ project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ description="[null]" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.apache.struts:struts-core" name="Struts Core" uuid="B" project_uuid="A"
+ module_uuid="[null]" module_uuid_path=".A."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.apache.struts:struts-core"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC"
+ kee="org.apache.struts:struts-core:/src/org/struts"
+ name="org.struts" root_id="2" uuid="C" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts-core:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="org.apache.struts:struts-core:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="2" uuid="D" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.apache.struts:struts-core:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Second sub project THAT HAS A DIFFERENT GROUP ID => MUST NOT BE UPDATED **************** -->
+ <projects id="5" root_id="1" kee="foo:struts-ui" name="Struts UI" uuid="E" project_uuid="[null]" module_uuid="[null]"
+ module_uuid_path=".E."
+ scope="PRJ" qualifier="BRC" long_name="Struts UI"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="foo:struts-ui"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="foo:struts-ui:/src/org/struts"
+ name="org.struts" root_id="5" uuid="F" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="foo:struts-ui:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA"
+ kee="foo:struts-ui:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="5" uuid="G" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="foo:struts-ui:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml
new file mode 100644
index 00000000000..5021b0c56bc
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml
@@ -0,0 +1,63 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts" uuid="A"
+ project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ description="[null]" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core" uuid="B" project_uuid="A"
+ module_uuid="[null]" module_uuid_path=".A."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.struts:struts-core"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts-core:/src/org/struts"
+ name="org.struts" root_id="2" uuid="C" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-core:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts-core:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="2" uuid="D" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-core:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Second sub project THAT HAS A DIFFERENT GROUP ID => MUST NOT BE UPDATED **************** -->
+ <projects id="5" root_id="1" kee="foo:struts-ui" name="Struts UI" uuid="E" project_uuid="[null]" module_uuid="[null]"
+ module_uuid_path=".E."
+ scope="PRJ" qualifier="BRC" long_name="Struts UI"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="foo:struts-ui"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="foo:struts-ui:/src/org/struts"
+ name="org.struts" root_id="5" uuid="F" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="foo:struts-ui:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA"
+ kee="foo:struts-ui:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="5" uuid="G" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="foo:struts-ui:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml
new file mode 100644
index 00000000000..a7a9d68d42a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/component/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml
@@ -0,0 +1,74 @@
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts" uuid="A"
+ project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ description="[null]" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <!-- ONLY THIS PROJECT MUST HAVE BEEN UPDATED -->
+ <!-- -->
+ <projects id="2" root_id="1" kee="struts:core" name="Struts Core" uuid="B" project_uuid="A" module_uuid="[null]"
+ module_uuid_path=".A."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="struts:core"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="DIR" kee="struts:core:/src/org/struts"
+ name="org.struts" root_id="2" uuid="C" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="struts:core:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA"
+ kee="struts:core:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="2" uuid="D" project_uuid="A" module_uuid="B" module_uuid_path=".A.B."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="struts:core:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.struts:struts-ui" name="Struts UI" uuid="E" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".E."
+ scope="PRJ" qualifier="BRC" long_name="Struts UI"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="org.struts:struts-ui"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="DIR" kee="org.struts:struts-ui:/src/org/struts"
+ name="org.struts" root_id="5" uuid="F" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-ui:org.struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA"
+ kee="org.struts:struts-ui:/src/org/struts/RequestContext.java"
+ name="RequestContext" root_id="5" uuid="G" project_uuid="[null]" module_uuid="[null]" module_uuid_path=".E."
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" created_at="[null]"
+ path="[null]" deprecated_kee="org.struts:struts-ui:org.struts.RequestContext"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core" uuid="H" project_uuid="[null]"
+ module_uuid="[null]" module_uuid_path=".H."
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ created_at="[null]" path="[null]" deprecated_kee="foo:struts-core"
+ authorization_updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/empty.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/empty.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/empty.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldGetMaxOrderIndexForNullUser.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldGetMaxOrderIndexForNullUser.xml
new file mode 100644
index 00000000000..1a3d92374b6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldGetMaxOrderIndexForNullUser.xml
@@ -0,0 +1,21 @@
+<dataset>
+
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="[null]"
+ order_index="1"/>
+
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="[null]"
+ order_index="15"/>
+
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="3"
+ order_index="25"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert-result.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert-result.xml
new file mode 100644
index 00000000000..75fb1670ce7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert-result.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <active_dashboards
+ id="1"
+ dashboard_id="2"
+ user_id="3"
+ order_index="4"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsert.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsertWithNoUser-result.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsertWithNoUser-result.xml
new file mode 100644
index 00000000000..a247a4f7c4d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldInsertWithNoUser-result.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <active_dashboards
+ id="1"
+ dashboard_id="2"
+ user_id="[null]"
+ order_index="4"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForAnonymous.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForAnonymous.xml
new file mode 100644
index 00000000000..0fbe3bcf9ec
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForAnonymous.xml
@@ -0,0 +1,68 @@
+<dataset>
+
+ <users id="42" login="obiwan" name="Obiwan" email="obiwan@keno.bi"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+
+ <dashboards
+ id="1"
+ user_id="1"
+ name="My Dashboard"
+ description="Dashboard shared by admin"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="Default Dashboard"
+ description="Dashboard provided by system"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="3"
+ user_id="[null]"
+ name="Project Dashboard"
+ description="Won't appear, not global"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="4"
+ user_id="[null]"
+ name="User Dashboard"
+ description="Won't appear, not anonymous"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+
+ <!-- Dashboard with ID 1 appears after ID 2 -->
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="[null]"
+ order_index="2"/>
+ <!-- Dashboard with ID 2 appears before ID 1 -->
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 3 does not appear (not global) -->
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 4 does not appear (not anonymous) -->
+ <active_dashboards
+ id="4"
+ dashboard_id="4"
+ user_id="42"
+ order_index="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForUser.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForUser.xml
new file mode 100644
index 00000000000..fd674277db0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectDashboardsForUser.xml
@@ -0,0 +1,85 @@
+<dataset>
+
+ <users id="24" login="anakin" name="Anakin" email="anakin@skywalk.er"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+ <users id="42" login="obiwan" name="Obiwan" email="obiwan@keno.bi"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+
+ <dashboards
+ id="1"
+ user_id="1"
+ name="My Dashboard"
+ description="Dashboard shared by admin"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="Default Dashboard"
+ description="Dashboard provided by system"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="3"
+ user_id="[null]"
+ name="Project Dashboard"
+ description="Won't appear, not global"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="4"
+ user_id="[null]"
+ name="Anonymous Dashboard"
+ description="Won't appear, anonymous"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="5"
+ user_id="[null]"
+ name="Another User Dashboard"
+ description="Won't appear, different user"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+
+ <!-- Dashboard with ID 1 appears after ID 2 -->
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="42"
+ order_index="2"/>
+ <!-- Dashboard with ID 2 appears before ID 1 -->
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="42"
+ order_index="1"/>
+ <!-- Dashboard with ID 3 does not appear (not global) -->
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="42"
+ order_index="1"/>
+ <!-- Dashboard with ID 4 does not appear (anonymous) -->
+ <active_dashboards
+ id="4"
+ dashboard_id="4"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 5 does not appear (another user) -->
+ <active_dashboards
+ id="5"
+ dashboard_id="5"
+ user_id="24"
+ order_index="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml
new file mode 100644
index 00000000000..0160b3a25d4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml
@@ -0,0 +1,68 @@
+<dataset>
+
+ <users id="42" login="obiwan" name="Obiwan" email="obiwan@keno.bi"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+
+ <dashboards
+ id="1"
+ user_id="1"
+ name="My Dashboard"
+ description="Dashboard shared by admin"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="Default Dashboard"
+ description="Dashboard provided by system"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="3"
+ user_id="[null]"
+ name="Project Dashboard"
+ description="Won't appear, global"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="4"
+ user_id="[null]"
+ name="User Dashboard"
+ description="Won't appear, not anonymous"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+
+ <!-- Dashboard with ID 1 appears after ID 2 -->
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="[null]"
+ order_index="2"/>
+ <!-- Dashboard with ID 2 appears before ID 1 -->
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 3 does not appear (global) -->
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 4 does not appear (not anonymous) -->
+ <active_dashboards
+ id="4"
+ dashboard_id="4"
+ user_id="42"
+ order_index="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml
new file mode 100644
index 00000000000..3f70d81896f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml
@@ -0,0 +1,85 @@
+<dataset>
+
+ <users id="24" login="anakin" name="Anakin" email="anakin@skywalk.er"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+ <users id="42" login="obiwan" name="Obiwan" email="obiwan@keno.bi"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+
+ <dashboards
+ id="1"
+ user_id="1"
+ name="My Dashboard"
+ description="Dashboard shared by admin"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="Default Dashboard"
+ description="Dashboard provided by system"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="3"
+ user_id="[null]"
+ name="Project Dashboard"
+ description="Won't appear, global"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="4"
+ user_id="[null]"
+ name="Anonymous Dashboard"
+ description="Won't appear, anonymous"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="5"
+ user_id="[null]"
+ name="Another User Dashboard"
+ description="Won't appear, different user"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+
+ <!-- Dashboard with ID 1 appears after ID 2 -->
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="42"
+ order_index="2"/>
+ <!-- Dashboard with ID 2 appears before ID 1 -->
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="42"
+ order_index="1"/>
+ <!-- Dashboard with ID 3 does not appear (global) -->
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="42"
+ order_index="1"/>
+ <!-- Dashboard with ID 4 does not appear (anonymous) -->
+ <active_dashboards
+ id="4"
+ dashboard_id="4"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 5 does not appear (another user) -->
+ <active_dashboards
+ id="5"
+ dashboard_id="5"
+ user_id="24"
+ order_index="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert-result.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert-result.xml
new file mode 100644
index 00000000000..86d9180988f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert-result.xml
@@ -0,0 +1,32 @@
+<dataset>
+
+ <dashboards
+ id="1"
+ user_id="6"
+ name="My Dashboard"
+ description="This is a dashboard"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+
+ <widgets
+ id="1"
+ dashboard_id="1"
+ widget_key="code_coverage"
+ name="Code coverage"
+ description="Widget for code coverage"
+ column_index="13"
+ row_index="14"
+ configured="[true]"
+ resource_id="[null]"
+ />
+
+ <widget_properties
+ id="1"
+ widget_id="1"
+ kee="displayITs"
+ text_value="true"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsert.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml
new file mode 100644
index 00000000000..00257e4b937
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml
@@ -0,0 +1,36 @@
+<dataset>
+
+ <dashboards
+ id="1"
+ user_id="[null]"
+ name="[null]"
+ description="[null]"
+ column_layout="[null]"
+ shared="[true]"
+ is_global="[false]"
+ created_at="[null]"
+ updated_at="[null]"
+ />
+
+ <widgets
+ id="1"
+ dashboard_id="1"
+ widget_key="code_coverage"
+ name="[null]"
+ description="[null]"
+ column_index="[null]"
+ row_index="[null]"
+ configured="[true]"
+ created_at="[null]"
+ updated_at="[null]"
+ resource_id="[null]"
+ />
+
+ <widget_properties
+ id="1"
+ widget_id="1"
+ kee="[null]"
+ text_value="[null]"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldSelectGlobalDashboard.xml b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldSelectGlobalDashboard.xml
new file mode 100644
index 00000000000..110e760f9ad
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/dashboard/DashboardDaoTest/shouldSelectGlobalDashboard.xml
@@ -0,0 +1,21 @@
+<dataset>
+
+ <dashboards
+ id="1"
+ user_id="6"
+ name="SQALE"
+ description="User SQALE dashboard"
+ column_layout="100%"
+ shared="[true]"
+ />
+
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="SQALE"
+ description="Global SQALE dashboard"
+ column_layout="100%"
+ shared="[true]"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/insert_characteristic-result.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/insert_characteristic-result.xml
new file mode 100644
index 00000000000..f0ac4524f65
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/insert_characteristic-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <characteristics id="1" kee="COMPILER_RELATED_PORTABILITY" name="Compiler related portability" parent_id="[null]"
+ characteristic_order="1" enabled="[true]"
+ created_at="2013-11-20" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics.xml
new file mode 100644
index 00000000000..8a6e7472bc4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics.xml
@@ -0,0 +1,24 @@
+<dataset>
+
+ <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="1"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <characteristics id="2" kee="COMPILER_RELATED_PORTABILITY" name="Compiler related portability" parent_id="1"
+ characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- requirement -->
+ <characteristics id="3" kee="[null]" name="[null]" parent_id="2" rule_id="1"
+ function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="[null]"/>
+
+ <!-- disabled characteristics -->
+ <characteristics id="4" kee="DISABLED_CHARACTERISTIC" name="Disabled characteristic" parent_id="1"
+ characteristic_order="2"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics_order_by_characteristic_order.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics_order_by_characteristic_order.xml
new file mode 100644
index 00000000000..59f106fe411
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_enabled_root_characteristics_order_by_characteristic_order.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="2"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <characteristics id="2" kee="TESTABILITY" name="Testability" parent_id="[null]" characteristic_order="1"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <characteristics id="3" kee="MAINTAINABILITY" name="Maintainability" parent_id="[null]" characteristic_order="4"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_max_characteristic_order_when_characteristics_are_all_disabled.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_max_characteristic_order_when_characteristics_are_all_disabled.xml
new file mode 100644
index 00000000000..d703591428a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_max_characteristic_order_when_characteristics_are_all_disabled.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <!-- Disabled root characteristic -->
+ <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="1"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled root characteristic -->
+ <characteristics id="2" kee="DISABLED_ROOT_CHARACTERISTIC" name="Disabled root characteristic" parent_id="[null]"
+ characteristic_order="2"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_sub_characteristics_by_parent_id.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_sub_characteristics_by_parent_id.xml
new file mode 100644
index 00000000000..60aa6d6fdfb
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/select_sub_characteristics_by_parent_id.xml
@@ -0,0 +1,35 @@
+<dataset>
+
+ <!-- Root characteristic -->
+ <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="1"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Sub characteristics of root characteristic -->
+ <characteristics id="2" kee="COMPILER_RELATED_PORTABILITY" name="Compiler related portability" parent_id="1"
+ characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+ <characteristics id="3" kee="HARDWARE_RELATED_PORTABILITY" name="Hardware related portability " parent_id="1"
+ characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Other sub characteristic -->
+ <characteristics id="4" kee="READABILITY" name="Readability" parent_id="5" characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled root characteristic -->
+ <characteristics id="10" kee="DISABLED_ROOT_CHARACTERISTIC" name="Disabled root characteristic" parent_id="[null]"
+ characteristic_order="2"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled characteristic -->
+ <characteristics id="11" kee="DISABLED_CHARACTERISTIC" name="Disabled characteristic" parent_id="10"
+ characteristic_order="[null]"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/shared.xml
new file mode 100644
index 00000000000..284d01bed45
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/shared.xml
@@ -0,0 +1,26 @@
+<dataset>
+
+ <!-- Root characteristic -->
+ <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="1"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Characteristic -->
+ <characteristics id="2" kee="COMPILER_RELATED_PORTABILITY" name="Compiler related portability" parent_id="1"
+ characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled root characteristic -->
+ <characteristics id="4" kee="DISABLED_ROOT_CHARACTERISTIC" name="Disabled root characteristic" parent_id="[null]"
+ characteristic_order="2"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled characteristic -->
+ <characteristics id="5" kee="DISABLED_CHARACTERISTIC" name="Disabled characteristic" parent_id="4"
+ characteristic_order="[null]"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic-result.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic-result.xml
new file mode 100644
index 00000000000..ff12f94cee6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <characteristics id="1" kee="COMPILER_RELATED_PORTABILITY" name="New name" parent_id="[null]" characteristic_order="2"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2014-03-19"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic.xml b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic.xml
new file mode 100644
index 00000000000..afa53fb186d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/CharacteristicDaoTest/update_characteristic.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <characteristics id="1" kee="COMPILER_RELATED_PORTABILITY" name="Compiler related portability" parent_id="[null]"
+ characteristic_order="1" enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-20"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/select_requirement.xml b/sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/select_requirement.xml
new file mode 100644
index 00000000000..1a71559a016
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/select_requirement.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <!-- Requirement -->
+ <characteristics id="3" kee="[null]" name="[null]" parent_id="2" root_id="1" rule_id="10"
+ characteristic_order="[null]"
+ function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/shared.xml
new file mode 100644
index 00000000000..0ee097f0869
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/debt/RequirementDaoTest/shared.xml
@@ -0,0 +1,17 @@
+<dataset>
+
+ <!-- Requirement -->
+ <characteristics id="3" kee="[null]" name="[null]" parent_id="2" root_id="1" rule_id="10"
+ characteristic_order="[null]"
+ function_key="linear_offset" factor_value="20.0" factor_unit="mn" offset_value="30.0" offset_unit="h"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="[null]"/>
+
+ <!-- Disabled requirement -->
+ <characteristics id="6" kee="[null]" name="[null]" parent_id="5" root_id="4" rule_id="10"
+ characteristic_order="[null]"
+ function_key="linear_offset" factor_value="30.0" factor_unit="mn" offset_value="20.0" offset_unit="h"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldGetByHash.xml b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldGetByHash.xml
new file mode 100644
index 00000000000..37efb6149f0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldGetByHash.xml
@@ -0,0 +1,61 @@
+<dataset>
+
+ <snapshots id="1" project_id="1" status="P" islast="0" purge_status="[null]"/>
+ <snapshots id="2" project_id="1" status="P" islast="0" purge_status="[null]"/>
+ <projects id="1" uuid="1" kee="bar-old" enabled="1" scope="FIL" qualifier="CLA" language="java"/>
+
+ <snapshots id="3" project_id="2" status="P" islast="1" purge_status="[null]"/>
+ <snapshots id="4" project_id="2" status="P" islast="1" purge_status="[null]"/>
+ <projects id="2" uuid="2" kee="bar-last" enabled="1" scope="FIL" qualifier="CLA" language="java"/>
+
+ <snapshots id="5" project_id="3" status="P" islast="0" purge_status="[null]"/>
+ <snapshots id="6" project_id="3" status="P" islast="0" purge_status="[null]"/>
+ <projects id="3" uuid="3" kee="foo-old" enabled="1" scope="FIL" qualifier="CLA" language="java"/>
+
+ <snapshots id="7" project_id="4" status="P" islast="1" purge_status="[null]"/>
+ <snapshots id="8" project_id="4" status="P" islast="1" purge_status="[null]"/>
+ <projects id="4" uuid="4" kee="foo-last" enabled="1" scope="FIL" qualifier="CLA" language="java"/>
+
+ <snapshots id="9" project_id="5" status="U" islast="0" purge_status="[null]"/>
+ <snapshots id="10" project_id="5" status="U" islast="0" purge_status="[null]"/>
+ <projects id="5" uuid="5" kee="foo" enabled="1" scope="FIL" qualifier="CLA" language="java"/>
+
+ <snapshots id="11" project_id="6" purge_status="[null]" status="P" islast="1"/>
+ <projects id="6" uuid="6" kee="baz" enabled="1" scope="FIL" qualifier="CLA" language="grvy"/>
+
+ <!-- Old snapshot of another project -->
+ <!-- bar-old -->
+ <duplications_index id="1" project_snapshot_id="1" snapshot_id="2" hash="bb" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+ <!-- Last snapshot of another project -->
+ <!-- bar-last -->
+ <duplications_index id="2" project_snapshot_id="3" snapshot_id="4" hash="aa" index_in_file="0" start_line="1"
+ end_line="2"/>
+
+ <!-- Old snapshot of current project -->
+ <!-- foo-old -->
+ <duplications_index id="3" project_snapshot_id="5" snapshot_id="6" hash="bb" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+ <!-- Last snapshot of current project -->
+ <!-- foo-last -->
+ <duplications_index id="4" project_snapshot_id="7" snapshot_id="8" hash="aa" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+ <!-- New snapshot of current project -->
+ <!-- foo -->
+ <duplications_index id="5" project_snapshot_id="9" snapshot_id="10" hash="aa" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+ <!-- Note that there is two blocks with same hash for current analysis to verify that we use "SELECT DISTINCT", -->
+ <!-- without "DISTINCT" we will select block from "bar-last" two times. -->
+ <duplications_index id="6" project_snapshot_id="9" snapshot_id="10" hash="aa" index_in_file="1" start_line="1"
+ end_line="1"/>
+
+ <!-- Last snapshot of project with another language -->
+ <!-- baz -->
+ <duplications_index id="7" project_snapshot_id="1" snapshot_id="11" hash="aa" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert-result.xml b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert-result.xml
new file mode 100644
index 00000000000..797be8db59d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert-result.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <snapshots purge_status="[null]" id="1" status="U" islast="0" project_id="0"/>
+ <snapshots purge_status="[null]" id="2" status="U" islast="0" project_id="1"/>
+ <projects id="1" uuid="1" kee="foo" enabled="1" scope="FIL" qualifier="CLA"/>
+
+ <duplications_index id="1" project_snapshot_id="1" snapshot_id="2" hash="bb" index_in_file="0" start_line="1"
+ end_line="2"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert.xml b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert.xml
new file mode 100644
index 00000000000..fd4fdc7b948
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/duplication/DuplicationDaoTest/shouldInsert.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <snapshots purge_status="[null]" id="1" status="U" islast="0" project_id="0"/>
+ <snapshots purge_status="[null]" id="2" status="U" islast="0" project_id="1"/>
+ <projects id="1" uuid="1" kee="foo" enabled="1" scope="FIL" qualifier="CLA"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/shared.xml
new file mode 100644
index 00000000000..7e2de0d8076
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/shared.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <projects id="1" kee="org.sonar.Sample" root_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan-result.xml
new file mode 100644
index 00000000000..2b855c97f05
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="Long term" description="Long term action plan" deadline="[null]"
+ user_login="arthur" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan.xml
new file mode 100644
index 00000000000..eaa959cccf9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_delete_action_plan.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="Long term" description="Long term action plan" deadline="[null]"
+ user_login="arthur" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="2" kee="BCD" project_id="1" name="Short term" description="Short term action plan" deadline="[null]"
+ user_login="arthur" status="CLOSED" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_key.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_key.xml
new file mode 100644
index 00000000000..c21cd9325e4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_key.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="[null]" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_keys.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_keys.xml
new file mode 100644
index 00000000000..a682d59fd17
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_keys.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="2" kee="ABD" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="3" kee="ABE" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="[null]" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_name_and_project_id.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_name_and_project_id.xml
new file mode 100644
index 00000000000..e99d5b3eb3a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_by_name_and_project_id.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="2" kee="ABD" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="3" kee="ABE" project_id="2" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_open_by_project_id.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_open_by_project_id.xml
new file mode 100644
index 00000000000..e15d4d0bbec
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_find_open_by_project_id.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="2" kee="ABD" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+ <action_plans id="3" kee="ABE" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="CLOSED" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_insert_new_action_plan-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_insert_new_action_plan-result.xml
new file mode 100644
index 00000000000..2b855c97f05
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_insert_new_action_plan-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="Long term" description="Long term action plan" deadline="[null]"
+ user_login="arthur" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan-result.xml
new file mode 100644
index 00000000000..2b855c97f05
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="Long term" description="Long term action plan" deadline="[null]"
+ user_login="arthur" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan.xml
new file mode 100644
index 00000000000..3da5c2083b5
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanDaoTest/should_update_action_plan.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="Old name" description="Old desc" deadline="[null]"
+ user_login="[null]" status="CLOSED" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/shared.xml
new file mode 100644
index 00000000000..7e2de0d8076
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/shared.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <projects id="1" kee="org.sonar.Sample" root_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/should_find_by_project.xml b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/should_find_by_project.xml
new file mode 100644
index 00000000000..f38c53fea63
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/ActionPlanStatsDaoTest/should_find_by_project.xml
@@ -0,0 +1,81 @@
+<dataset>
+
+ <action_plans id="1" kee="ABC" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="OPEN" created_at="[null]" updated_at="[null]"/>
+
+ <issues
+ id="100"
+ kee="ABCDE"
+ component_uuid="uuid-400"
+ project_uuid="uuid-400"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ status="OPEN"
+ resolution="[null]"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ action_plan_key="ABC"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+ <issues
+ id="101"
+ kee="ABCDF"
+ component_uuid="uuid-400"
+ project_uuid="uuid-400"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ status="CLOSED"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ action_plan_key="ABC"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+ <issues
+ id="102"
+ kee="ABCDG"
+ component_uuid="uuid-400"
+ project_uuid="uuid-400"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ status="CLOSED"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ action_plan_key="ABC"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete-result.xml
new file mode 100644
index 00000000000..be1549d36f1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete-result.xml
@@ -0,0 +1,42 @@
+<dataset>
+
+ <issue_changes
+ id="100"
+ kee="COMMENT-1"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="old comment"
+ created_at="1356994800000"
+ updated_at="1356994800000"
+ issue_change_creation_date="[null]"
+ />
+
+ <issue_changes
+ id="101"
+ kee="[null]"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1359759600000"
+ updated_at="1359759600000"
+ issue_change_creation_date="1359759600000"
+ />
+
+ <!--
+ DELETED
+
+ <issue_changes
+ id="102"
+ kee="COMMENT-2"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="recent comment"
+ created_at="1367704800000"
+ updated_at="1367704800000"
+ issue_change_creation_date="1367704800000"
+ />
+ -->
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete.xml
new file mode 100644
index 00000000000..3277acb54ca
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/delete.xml
@@ -0,0 +1,38 @@
+<dataset>
+
+ <issue_changes
+ id="100"
+ kee="COMMENT-1"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="old comment"
+ created_at="1356994800000"
+ updated_at="1356994800000"
+ issue_change_creation_date="[null]"
+ />
+
+ <issue_changes
+ id="101"
+ kee="[null]"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1359759600000"
+ updated_at="1359759600000"
+ issue_change_creation_date="1359759600000"
+ />
+
+ <issue_changes
+ id="102"
+ kee="COMMENT-2"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="recent comment"
+ created_at="1367704800000"
+ updated_at="1367704800000"
+ issue_change_creation_date="1367704800000"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/empty.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/empty.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/empty.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/insert-result.xml
new file mode 100644
index 00000000000..06468bff8a8
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/insert-result.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <issue_changes
+ id="1"
+ kee="EFGH"
+ issue_key="ABCDE"
+ user_login="emmerik"
+ change_type="comment"
+ change_data="Some text"
+ created_at="1500000000000"
+ updated_at="1501000000000"
+ issue_change_creation_date="1502000000000"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml
new file mode 100644
index 00000000000..ef1c47fe020
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/selectChangelogOfNonClosedIssuesByComponent.xml
@@ -0,0 +1,182 @@
+<dataset>
+
+ <!-- Unresolved. To be included -->
+ <issues
+ id="1"
+ kee="UNRESOLVED_ON_FILE_1"
+ component_uuid="FILE_1"
+ project_uuid="PROJECT_1"
+ resolution="[null]"
+ status="OPEN"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="[null]"
+ checksum="[null]"
+ reporter="user"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- diff -->
+ <issue_changes
+ id="100"
+ kee="100"
+ issue_key="UNRESOLVED_ON_FILE_1"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1410213600000"
+ updated_at="1410213600000"
+ issue_change_creation_date="1410213600000"
+ />
+
+ <!-- comment -->
+ <issue_changes
+ id="102"
+ kee="102"
+ issue_key="UNRESOLVED_ON_FILE_1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="recent comment"
+ created_at="1410213600000"
+ updated_at="1410213600000"
+ issue_change_creation_date="[null]"
+ />
+
+ <!-- Resolved but not closed. To be included -->
+ <issues
+ id="2"
+ kee="RESOLVED_ON_FILE_1"
+ component_uuid="FILE_1"
+ project_uuid="PROJECT_1"
+ resolution="FIXED"
+ status="RESOLVED"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="120"
+ effort_to_fix="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <issue_changes
+ id="103"
+ kee="103"
+ issue_key="RESOLVED_ON_FILE_1"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1410213600000"
+ updated_at="1410213600000"
+ issue_change_creation_date="1410213600000"
+ />
+
+ <!-- Closed. To be excluded -->
+ <issues
+ id="3"
+ kee="CLOSED_ON_FILE_1"
+ component_uuid="FILE_1"
+ project_uuid="PROJECT_1"
+ resolution="FIXED"
+ status="CLOSED"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="120"
+ effort_to_fix="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <issue_changes
+ id="104"
+ kee="104"
+ issue_key="CLOSED_ON_FILE_1"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1410213600000"
+ updated_at="1410213600000"
+ issue_change_creation_date="1410213600000"
+ />
+
+ <!-- Unresolved on other file -->
+ <issues
+ id="4"
+ kee="UNRESOLVED_ON_FILE_2"
+ component_uuid="FILE_2"
+ project_uuid="PROJECT_1"
+ resolution="[null]"
+ status="OPEN"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="[null]"
+ checksum="[null]"
+ reporter="user"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- diff -->
+ <issue_changes
+ id="105"
+ kee="105"
+ issue_key="UNRESOLVED_ON_FILE_2"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1410213600000"
+ updated_at="1410213600000"
+ issue_change_creation_date="1410213600000"
+ />
+
+ <!-- comment -->
+ <issue_changes
+ id="106"
+ kee="106"
+ issue_key="UNRESOLVED_ON_FILE_2"
+ user_login="arthur"
+ change_type="comment"
+ change_data="recent comment"
+ created_at="1410213600000"
+ updated_at="1410213600000"
+ issue_change_creation_date="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/shared.xml
new file mode 100644
index 00000000000..0b181deb146
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/shared.xml
@@ -0,0 +1,63 @@
+<dataset>
+
+ <issue_changes
+ id="100"
+ kee="ABCDE"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="comment"
+ change_data="old comment"
+ created_at="1356994800000"
+ updated_at="1356994800000"
+ issue_change_creation_date="[null]"
+ />
+
+ <issue_changes
+ id="101"
+ kee="[null]"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1359759600000"
+ updated_at="1359759600000"
+ issue_change_creation_date="1359759600000"
+ />
+
+ <issue_changes
+ id="102"
+ kee="FGHIJ"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="comment"
+ change_data="recent comment"
+ created_at="1367704800000"
+ updated_at="1367704800000"
+ issue_change_creation_date="[null]"
+ />
+
+ <issue_changes
+ id="103"
+ kee="KLMN"
+ issue_key="1001"
+ user_login="arthur"
+ change_type="diff"
+ change_data="actionPlan=1.0|1.1"
+ created_at="1359759600000"
+ updated_at="1359759600000"
+ issue_change_creation_date="1359759600000"
+ />
+
+ <issue_changes
+ id="104"
+ kee="OPQR"
+ issue_key="1001"
+ user_login="henry"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1356994800000"
+ updated_at="1356994800000"
+ issue_change_creation_date="1356994800000"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update-result.xml
new file mode 100644
index 00000000000..a1c436a6622
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update-result.xml
@@ -0,0 +1,38 @@
+<dataset>
+
+ <issue_changes
+ id="100"
+ kee="COMMENT-1"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="old comment"
+ created_at="1356994800000"
+ updated_at="1356994800000"
+ issue_change_creation_date="[null]"
+ />
+
+ <issue_changes
+ id="101"
+ kee="[null]"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1359759600000"
+ updated_at="1359759600000"
+ issue_change_creation_date="1359759600000"
+ />
+
+ <issue_changes
+ id="102"
+ kee="COMMENT-2"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="new comment"
+ created_at="1367704800000"
+ updated_at="1500000000000"
+ issue_change_creation_date="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update.xml
new file mode 100644
index 00000000000..641f333faa3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeDaoTest/update.xml
@@ -0,0 +1,38 @@
+<dataset>
+
+ <issue_changes
+ id="100"
+ kee="COMMENT-1"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="old comment"
+ created_at="1356994800000"
+ updated_at="1356994800000"
+ issue_change_creation_date="[null]"
+ />
+
+ <issue_changes
+ id="101"
+ kee="[null]"
+ issue_key="1000"
+ user_login="arthur"
+ change_type="diff"
+ change_data="severity=MAJOR|BLOCKER"
+ created_at="1359759600000"
+ updated_at="1359759600000"
+ issue_change_creation_date="1359759600000"
+ />
+
+ <issue_changes
+ id="102"
+ kee="COMMENT-2"
+ issue_key="ISSUE-1"
+ user_login="arthur"
+ change_type="comment"
+ change_data="old value"
+ created_at="1367704800000"
+ updated_at="1367704800000"
+ issue_change_creation_date="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_comment-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_comment-result.xml
new file mode 100644
index 00000000000..ed763c2952a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_comment-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+ <issue_changes
+ id="1"
+ kee="COMMENT-1234"
+ issue_key="ABCDE"
+ user_login="emmerik"
+ change_type="comment"
+ change_data="the comment"
+ created_at="1500000000000"
+ updated_at="1500000000000"
+ issue_change_creation_date="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_diff-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_diff-result.xml
new file mode 100644
index 00000000000..bbc0741292f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueChangeMapperTest/insert_diff-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+ <issue_changes
+ id="1"
+ kee="[null]"
+ issue_key="ABCDE"
+ user_login="emmerik"
+ change_type="diff"
+ change_data="severity=INFO|BLOCKER"
+ created_at="1500000000000"
+ updated_at="1500000000000"
+ issue_change_creation_date="1500000000000"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/find_severities_by_component.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/find_severities_by_component.xml
new file mode 100644
index 00000000000..deb88387a4a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/find_severities_by_component.xml
@@ -0,0 +1,80 @@
+<dataset>
+
+ <!-- rule 500 -->
+ <issues
+ id="100"
+ kee="ABCDE-1"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="[null]"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+ <issues
+ id="101"
+ kee="ABCDE-2"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="500"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="[null]"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366149600000"
+ issue_update_date="1366149600000"
+ issue_close_date="1366149600000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+
+ <!-- rule 501 -->
+ <issues
+ id="102"
+ kee="ABCDE-3"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="501"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="[null]"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/shared.xml
new file mode 100644
index 00000000000..353f9651342
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/shared.xml
@@ -0,0 +1,26 @@
+<dataset>
+
+ <group_roles id="1" group_id="[null]" resource_id="399" role="user"/>
+
+ <projects id="399" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="." kee="struts"
+ root_id="[null]" qualifier="TRK" scope="PRJ"/>
+ <projects id="400" uuid="BCDE" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD." kee="struts-core"
+ root_id="399" qualifier="BRC" scope="PRJ"/>
+ <projects id="401" uuid="CDEF" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE." kee="Action.java"
+ root_id="400" qualifier="CLA" scope="PRJ"/>
+ <projects id="402" uuid="DEFG" project_uuid="ABCD" module_uuid="BCDE" module_uuid_path=".ABCD.BCDE." kee="Filter.java"
+ root_id="400" qualifier="CLA" scope="PRJ"/>
+
+ <snapshots id="100" project_id="399" root_snapshot_id="[null]" parent_snapshot_id="[null]" root_project_id="399"
+ path="" islast="[true]"/>
+ <snapshots id="101" project_id="400" root_snapshot_id="100" parent_snapshot_id="100" root_project_id="399" path="100."
+ islast="[true]"/>
+ <snapshots id="102" project_id="401" root_snapshot_id="100" parent_snapshot_id="101" root_project_id="399"
+ path="100.101." islast="[true]"/>
+ <snapshots id="103" project_id="402" root_snapshot_id="100" parent_snapshot_id="101" root_project_id="399"
+ path="100.101." islast="[true]"/>
+
+ <rules id="500" tags="[null]" system_tags="[null]" plugin_rule_key="AvoidCycle" plugin_name="squid" language="java"/>
+ <rules id="501" tags="[null]" system_tags="[null]" plugin_rule_key="NullRef" plugin_name="squid" language="xoo"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_issue_and_component_ids.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_issue_and_component_ids.xml
new file mode 100644
index 00000000000..f92b8328c21
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_issue_and_component_ids.xml
@@ -0,0 +1,80 @@
+<dataset>
+
+ <!-- rule 500 -->
+ <issues
+ id="100"
+ kee="ABCDE-1"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+ <issues
+ id="101"
+ kee="ABCDE-2"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+
+
+ <!-- rule 501 -->
+ <issues
+ id="102"
+ kee="ABCDE-3"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="501"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="1400000000000"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module.xml
new file mode 100644
index 00000000000..b3575610bd1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module.xml
@@ -0,0 +1,133 @@
+<dataset>
+
+ <!-- Open Issue on a file -->
+ <issues
+ id="100"
+ kee="100"
+ component_uuid="CDEF"
+ project_uuid="ABCD"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="user"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Open Issue on a file -->
+ <issues
+ id="101"
+ kee="101"
+ component_uuid="DEFG"
+ project_uuid="ABCD"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="120"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Closed Issue on a file -->
+ <issues
+ id="102"
+ kee="102"
+ component_uuid="DEFG"
+ project_uuid="ABCD"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="120"
+ effort_to_fix="[null]"
+ status="CLOSED"
+ resolution="FIXED"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Open Issue on a sub module -->
+ <issues
+ id="103"
+ kee="103"
+ component_uuid="BCDE"
+ project_uuid="ABCD"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="[null]"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Open Issue on a root module -->
+ <issues
+ id="104"
+ kee="104"
+ component_uuid="ABCD"
+ project_uuid="ABCD"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="[null]"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml
new file mode 100644
index 00000000000..b7fa286ffca
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml
@@ -0,0 +1,127 @@
+<!--
+ ~ SonarQube, open source software quality management tool.
+ ~ Copyright (C) 2008-2014 SonarSource
+ ~ mailto:contact AT sonarsource DOT com
+ ~
+ ~ SonarQube 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.
+ ~
+ ~ SonarQube 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 program; if not, write to the Free Software Foundation,
+ ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ -->
+
+<dataset>
+
+ <!-- Open Issue on a file -->
+ <issues
+ id="100"
+ kee="100"
+ component_uuid="CDEF"
+ project_uuid="uuid-111"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="user"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Open Issue on a file -->
+ <issues
+ id="101"
+ kee="101"
+ component_uuid="DEFG"
+ project_uuid="uuid-111"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="120"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Closed Issue on a file -->
+ <issues
+ id="102"
+ kee="102"
+ component_uuid="DEFG"
+ project_uuid="uuid-111"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="120"
+ effort_to_fix="[null]"
+ status="CLOSED"
+ resolution="FIXED"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+ <!-- Open Issue on a sub module -->
+ <issues
+ id="103"
+ kee="103"
+ component_uuid="BCDE"
+ project_uuid="uuid-111"
+ rule_id="501"
+ severity="MAJOR"
+ manual_severity="[false]"
+ message="[null]"
+ line="[null]"
+ effort_to_fix="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ assignee="user"
+ author_login="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="1400000000000"
+ updated_at="[null]"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/shared.xml
new file mode 100644
index 00000000000..68f9cdc6615
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/shared.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <issue_filters
+ id="1"
+ name="Sonar Issues"
+ user_login="stephane"
+ shared="[true]"
+ description="All issues of Sonar"
+ data="componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="2"
+ name="Open issues"
+ user_login="michael"
+ shared="[false]"
+ description="All open issues"
+ data="statuses=OPEN"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_delete-result.xml
new file mode 100644
index 00000000000..7933167cf9a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_delete-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <issue_filters
+ id="2"
+ name="Open issues"
+ user_login="michael"
+ shared="[false]"
+ description="All open issues"
+ data="statuses=OPEN"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_insert-result.xml
new file mode 100644
index 00000000000..869df62a337
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_insert-result.xml
@@ -0,0 +1,34 @@
+<dataset>
+
+ <issue_filters
+ id="1"
+ name="Sonar Issues"
+ user_login="stephane"
+ shared="[true]"
+ description="All issues of Sonar"
+ data="componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="2"
+ name="Open issues"
+ user_login="michael"
+ shared="[false]"
+ description="All open issues"
+ data="statuses=OPEN"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+
+ <issue_filters
+ id="3"
+ name="Sonar Open issues"
+ user_login="michael"
+ shared="[true]"
+ description="All open issues on Sonar"
+ data="statuses=OPEN|componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user.xml
new file mode 100644
index 00000000000..0f91cec3cdd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user.xml
@@ -0,0 +1,33 @@
+<dataset>
+
+ <issue_filters
+ id="1"
+ name="Sonar Issues"
+ user_login="stephane"
+ shared="[true]"
+ description="All issues of Sonar"
+ data="componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="2"
+ name="Open issues"
+ user_login="michael"
+ shared="[false]"
+ description="All open issues"
+ data="statuses=OPEN"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="3"
+ name="Sonar Open issues"
+ user_login="michael"
+ shared="[true]"
+ description="All open issues on Sonar"
+ data="statuses=OPEN|componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user_with_only_favorite_filters.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user_with_only_favorite_filters.xml
new file mode 100644
index 00000000000..47d558307c3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_by_user_with_only_favorite_filters.xml
@@ -0,0 +1,39 @@
+<dataset>
+
+ <issue_filters
+ id="1"
+ name="Sonar Issues"
+ user_login="stephane"
+ shared="[true]"
+ description="All issues of Sonar"
+ data="componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="2"
+ name="Open issues"
+ user_login="michael"
+ shared="[false]"
+ description="All open issues"
+ data="statuses=OPEN"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="3"
+ name="Sonar Open issues"
+ user_login="michael"
+ shared="[true]"
+ description="All open issues on Sonar"
+ data="statuses=OPEN|componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="10"
+ user_login="michael"
+ issue_filter_id="2"
+ created_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_provided_by_name.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_provided_by_name.xml
new file mode 100644
index 00000000000..81bf84aee21
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_select_provided_by_name.xml
@@ -0,0 +1,58 @@
+<dataset>
+
+ <!-- This one must be found -->
+ <issue_filters
+ id="1"
+ name="Unresolved Issues"
+ user_login="[null]"
+ shared="[true]"
+ description="[null]"
+ data="resolved=false"
+ created_at="2011-04-25 01:15:00"
+ updated_at="2011-04-25 01:15:00"/>
+
+ <!-- This one must NOT be found: belongs to admin -->
+ <issue_filters
+ id="2"
+ name="Unresolved Issues"
+ user_login="admin"
+ shared="[true]"
+ description="[null]"
+ data="resolved=false"
+ created_at="2011-04-25 01:15:00"
+ updated_at="2011-04-25 01:15:00"/>
+
+ <!-- This one must NOT be found: not shared -->
+ <issue_filters
+ id="3"
+ name="Unresolved Issues"
+ user_login="[null]"
+ shared="[false]"
+ description="[null]"
+ data="resolved=false"
+ created_at="2011-04-25 01:15:00"
+ updated_at="2011-04-25 01:15:00"/>
+
+ <!-- This one must be found -->
+ <issue_filters
+ id="4"
+ name="My Unresolved Issues"
+ user_login="[null]"
+ shared="[true]"
+ description="[null]"
+ data="resolved=false|assignees=__me__"
+ created_at="2011-04-25 01:15:00"
+ updated_at="2011-04-25 01:15:00"/>
+
+ <!-- This one must NOT be found: not shared -->
+ <issue_filters
+ id="5"
+ name="Unknown Filter"
+ user_login="[null]"
+ shared="[false]"
+ description="[null]"
+ data="resolved=false"
+ created_at="2011-04-25 01:15:00"
+ updated_at="2011-04-25 01:15:00"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_update-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_update-result.xml
new file mode 100644
index 00000000000..0916ece3f0a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterDaoTest/should_update-result.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <issue_filters
+ id="1"
+ name="Sonar Issues"
+ user_login="stephane"
+ shared="[true]"
+ description="All issues of Sonar"
+ data="componentRoots=org.codehaus.sonar"
+ created_at="2013-06-10"
+ updated_at="2013-06-10"/>
+
+ <issue_filters
+ id="2"
+ name="Closed issues"
+ user_login="bernard"
+ shared="[false]"
+ description="All closed issues"
+ data="statuses=CLOSED"
+ created_at="2013-06-10"
+ updated_at="2013-06-11"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/shared.xml
new file mode 100644
index 00000000000..07d23d9fce6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/shared.xml
@@ -0,0 +1,21 @@
+<dataset>
+
+ <issue_filter_favourites
+ id="1"
+ user_login="stephane"
+ issue_filter_id="10"
+ created_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="2"
+ user_login="stephane"
+ issue_filter_id="11"
+ created_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="3"
+ user_login="arthur"
+ issue_filter_id="10"
+ created_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete-result.xml
new file mode 100644
index 00000000000..b3f03e6724e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete-result.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <issue_filter_favourites
+ id="1"
+ user_login="stephane"
+ issue_filter_id="10"
+ created_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="2"
+ user_login="stephane"
+ issue_filter_id="11"
+ created_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml
new file mode 100644
index 00000000000..c2a7f43e9e4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_delete_by_issue_filter_id-result.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <issue_filter_favourites
+ id="2"
+ user_login="stephane"
+ issue_filter_id="11"
+ created_at="2013-06-10"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_insert-result.xml
new file mode 100644
index 00000000000..f9b8e27974d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueFilterFavouriteDaoTest/should_insert-result.xml
@@ -0,0 +1,27 @@
+<dataset>
+
+ <issue_filter_favourites
+ id="1"
+ user_login="stephane"
+ issue_filter_id="10"
+ created_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="2"
+ user_login="stephane"
+ issue_filter_id="11"
+ created_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="3"
+ user_login="arthur"
+ issue_filter_id="10"
+ created_at="2013-06-10"/>
+
+ <issue_filter_favourites
+ id="4"
+ user_login="arthur"
+ issue_filter_id="11"
+ created_at="2013-06-18"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testInsert-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testInsert-result.xml
new file mode 100644
index 00000000000..816b527458c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testInsert-result.xml
@@ -0,0 +1,29 @@
+<dataset>
+ <issues
+ id="100"
+ kee="ABCDE"
+ component_uuid="uuid-123"
+ project_uuid="uuid-100"
+ rule_id="200"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="the message"
+ line="500"
+ effort_to_fix="3.14"
+ technical_debt="10"
+ status="RESOLVED"
+ resolution="FIXED"
+ checksum="123456789"
+ reporter="emmerik"
+ author_login="morgan"
+ assignee="karadoc"
+ issue_attributes="JIRA=FOO-1234"
+ tags="[null]"
+ issue_creation_date="1401000000000"
+ issue_update_date="1402000000000"
+ issue_close_date="1403000000000"
+ created_at="1400000000000"
+ updated_at="1500000000000"
+ action_plan_key="current_sprint"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate-result.xml
new file mode 100644
index 00000000000..afce8a1ed86
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate-result.xml
@@ -0,0 +1,29 @@
+<dataset>
+ <issues
+ id="100"
+ kee="ABCDE"
+ component_uuid="uuid-123"
+ project_uuid="uuid-101"
+ rule_id="200"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="the message"
+ line="500"
+ effort_to_fix="3.14"
+ technical_debt="10"
+ status="RESOLVED"
+ resolution="FIXED"
+ checksum="123456789"
+ reporter="emmerik"
+ author_login="morgan"
+ assignee="karadoc"
+ issue_attributes="JIRA=FOO-1234"
+ tags="[null]"
+ issue_creation_date="1401000000000"
+ issue_update_date="1402000000000"
+ issue_close_date="1403000000000"
+ created_at="1400000000000"
+ updated_at="1500000000000"
+ action_plan_key="current_sprint"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate.xml
new file mode 100644
index 00000000000..5cb05d1dd96
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/testUpdate.xml
@@ -0,0 +1,27 @@
+<dataset>
+ <issues
+ id="100"
+ kee="ABCDE"
+ component_uuid="uuid-123"
+ project_uuid="uuid-100"
+ rule_id="200"
+ severity="INFO"
+ manual_severity="[false]"
+ message="old"
+ line="[null]"
+ effort_to_fix="[null]"
+ technical_debt="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ author_login="[null]"
+ assignee="[null]"
+ issue_creation_date="1401000000000"
+ issue_update_date="1402000000000"
+ issue_close_date="1403000000000"
+ created_at="1400000000000"
+ updated_at="1500000000000"
+ action_plan_key="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml
new file mode 100644
index 00000000000..ecb26c85603
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml
@@ -0,0 +1,30 @@
+<dataset>
+ <!-- not updated -->
+ <issues
+ id="100"
+ kee="ABCDE"
+ component_uuid="uuid-123"
+ project_uuid="uuid-100"
+ rule_id="200"
+ severity="INFO"
+ manual_severity="[false]"
+ message="old"
+ line="[null]"
+ effort_to_fix="[null]"
+ technical_debt="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ author_login="[null]"
+ assignee="[null]"
+ issue_attributes="[null]"
+ tags="[null]"
+ issue_creation_date="[null]"
+ issue_update_date="[null]"
+ issue_close_date="[null]"
+ created_at="1400000000000"
+ updated_at="1450000000000"
+ action_plan_key="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml
new file mode 100644
index 00000000000..0264dc2bd7b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml
@@ -0,0 +1,28 @@
+<dataset>
+ <issues
+ id="100"
+ kee="ABCDE"
+ component_uuid="uuid-123"
+ project_uuid="uuid-100"
+ rule_id="200"
+ severity="INFO"
+ manual_severity="[false]"
+ message="old"
+ line="[null]"
+ effort_to_fix="[null]"
+ technical_debt="[null]"
+ status="OPEN"
+ resolution="[null]"
+ checksum="[null]"
+ reporter="[null]"
+ author_login="[null]"
+ assignee="[null]"
+ issue_attributes="[null]"
+ issue_creation_date="[null]"
+ issue_update_date="[null]"
+ issue_close_date="[null]"
+ created_at="1400000000000"
+ updated_at="1450000000000"
+ action_plan_key="[null]"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/issue/IssueStatsDaoTest/should_select_assignees.xml b/sonar-db/src/test/resources/org/sonar/db/issue/IssueStatsDaoTest/should_select_assignees.xml
new file mode 100644
index 00000000000..4cc9d077a16
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/issue/IssueStatsDaoTest/should_select_assignees.xml
@@ -0,0 +1,80 @@
+<dataset>
+
+ <group_roles id="1" group_id="[null]" resource_id="399" role="user"/>
+ <projects id="399" kee="my.project:kee" root_id="[null]" qualifier="TRK" scope="PRJ"/>
+
+ <issues
+ id="100"
+ kee="ABCDE-1"
+ component_uuid="uuid-401"
+ project_uuid="uuid-399"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="2013-04-16"
+ updated_at="2013-04-16"
+ />
+
+ <issues
+ id="101"
+ kee="ABCDE-2"
+ component_uuid="uuid-401"
+ project_uuid="uuid-399"
+ rule_id="500"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="[null]"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="2013-04-16"
+ updated_at="2013-04-16"
+ />
+
+ <issues
+ id="102"
+ kee="ABCDE-3"
+ component_uuid="uuid-401"
+ project_uuid="uuid-399"
+ rule_id="501"
+ severity="BLOCKER"
+ manual_severity="[false]"
+ message="[null]"
+ line="200"
+ effort_to_fix="4.2"
+ status="OPEN"
+ resolution="FIXED"
+ checksum="XXX"
+ reporter="arthur"
+ assignee="perceval"
+ author_login="[null]"
+ issue_attributes="JIRA=FOO-1234"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="2013-04-16"
+ updated_at="2013-04-16"
+ />
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldCountByTypeAndKey.xml b/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldCountByTypeAndKey.xml
new file mode 100644
index 00000000000..f4b6b01f4ab
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldCountByTypeAndKey.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <loaded_templates
+ id="1"
+ kee="HOTSPOTS"
+ template_type="DASHBOARD"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert-result.xml b/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert-result.xml
new file mode 100644
index 00000000000..6881dd82799
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <loaded_templates
+ id="1"
+ kee="SQALE"
+ template_type="DASHBOARD"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert.xml b/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/loadedtemplate/LoadedTemplateDaoTest/shouldInsert.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shared.xml
new file mode 100644
index 00000000000..8814a1c894d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shared.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <measure_filters
+ id="1"
+ name="Projects"
+ user_id="[null]"
+ shared="[true]"
+ description="All projects"
+ data="qualifiers=TRK"
+ created_at="2012-12-25"
+ updated_at="2012-12-25"/>
+
+ <measure_filters
+ id="2"
+ name="Files"
+ user_id="[null]"
+ shared="[true]"
+ description="All files"
+ data="qualifiers=FIL"
+ created_at="2012-01-25"
+ updated_at="2012-01-25"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shouldInsert-result.xml b/sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shouldInsert-result.xml
new file mode 100644
index 00000000000..1100b166b82
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/measure/MeasureFilterDaoTest/shouldInsert-result.xml
@@ -0,0 +1,34 @@
+<dataset>
+
+ <measure_filters
+ id="1"
+ name="Projects"
+ user_id="[null]"
+ shared="[true]"
+ description="All projects"
+ data="qualifiers=TRK"
+ created_at="2012-12-25"
+ updated_at="2012-12-25"/>
+
+ <measure_filters
+ id="2"
+ name="Files"
+ user_id="[null]"
+ shared="[true]"
+ description="All files"
+ data="qualifiers=FIL"
+ created_at="2012-01-25"
+ updated_at="2012-01-25"/>
+
+
+ <measure_filters
+ id="3"
+ name="Project Treemap"
+ user_id="123"
+ shared="[true]"
+ description="Treemap of projects"
+ data="qualifiers=TRK|display=treemap"
+ created_at="2012-12-25"
+ updated_at="2012-12-25"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification-result.xml b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification-result.xml
new file mode 100644
index 00000000000..81662a5913d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <notifications id="2" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDI="/>
+
+ <notifications id="4" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDQ="/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification.xml b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification.xml
new file mode 100644
index 00000000000..6611a054610
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_delete_notification.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <notifications id="1" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDE="/>
+
+ <notifications id="2" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDI="/>
+
+ <notifications id="3" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDM="/>
+
+ <notifications id="4" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDQ="/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_findOldest.xml b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_findOldest.xml
new file mode 100644
index 00000000000..6611a054610
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_findOldest.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <notifications id="1" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDE="/>
+
+ <notifications id="2" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDI="/>
+
+ <notifications id="3" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDM="/>
+
+ <notifications id="4" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDQ="/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml
new file mode 100644
index 00000000000..7afce2d8263
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/notification/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <notifications id="1" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAVlbWFpbA=="/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions.xml
new file mode 100644
index 00000000000..418d5e69fac
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions.xml
@@ -0,0 +1,20 @@
+<dataset>
+
+ <groups id="200" name="sonar-administrators" description="System administrators"/>
+ <groups id="201" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="202" name="sonar-reviewers" description="Reviewers"/>
+
+ <!-- Project permissions -->
+ <group_roles id="1" group_id="200" resource_id="100" role="user"/>
+ <group_roles id="2" group_id="200" resource_id="100" role="admin"/>
+ <group_roles id="3" group_id="200" resource_id="100" role="codeviewer"/>
+
+ <group_roles id="4" group_id="201" resource_id="100" role="user"/>
+
+ <!-- Permission 'user' for Anyone group -->
+ <group_roles id="5" group_id="[null]" resource_id="100" role="user"/>
+
+ <!-- Global permission -->
+ <group_roles id="10" group_id="200" resource_id="[null]" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml
new file mode 100644
index 00000000000..10ab0e21e3f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <groups id="200" name="sonar-reviewers" description="Reviewers"/>
+ <groups id="201" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="202" name="sonar-administrators" description="System administrators"/>
+
+ <!-- Project permissions -->
+ <group_roles id="1" group_id="200" resource_id="100" role="user"/>
+ <group_roles id="2" group_id="200" resource_id="100" role="admin"/>
+ <group_roles id="3" group_id="200" resource_id="100" role="codeviewer"/>
+
+ <!-- Permission for Anyone group -->
+ <group_roles id="4" group_id="[null]" resource_id="100" role="user"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions.xml
new file mode 100644
index 00000000000..8d6592cbc11
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <groups id="200" name="sonar-administrators" description="System administrators"/>
+ <groups id="201" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="202" name="sonar-reviewers" description="Reviewers"/>
+
+ <perm_templates_groups id="1" group_id="200" permission_reference="user" template_id="50"/>
+ <perm_templates_groups id="2" group_id="200" permission_reference="admin" template_id="50"/>
+ <perm_templates_groups id="3" group_id="200" permission_reference="codeviewer" template_id="50"/>
+
+ <perm_templates_groups id="4" group_id="201" permission_reference="user" template_id="50"/>
+
+ <!-- Permission 'user' for Anyone group -->
+ <perm_templates_groups id="5" group_id="[null]" permission_reference="user" template_id="50"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml
new file mode 100644
index 00000000000..baf745a5028
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/GroupWithPermissionTemplateDaoTest/groups_with_permissions_should_be_sorted_by_group_name.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <groups id="200" name="sonar-reviewers"/>
+ <groups id="201" name="sonar-users"/>
+ <groups id="202" name="sonar-administrators"/>
+
+ <perm_templates_groups id="1" group_id="200" permission_reference="user" template_id="50"/>
+ <perm_templates_groups id="2" group_id="200" permission_reference="admin" template_id="50"/>
+ <perm_templates_groups id="3" group_id="200" permission_reference="codeviewer" template_id="50"/>
+
+ <perm_templates_groups id="4" group_id="201" permission_reference="user" template_id="50"/>
+
+ <!-- Permission 'user' for Anyone group -->
+ <perm_templates_groups id="5" group_id="[null]" permission_reference="user" template_id="50"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission-result.xml
new file mode 100644
index 00000000000..793d16b3bae
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <user_roles id="1" user_id="200" resource_id="123" role="user"/>
+ <user_roles id="2" user_id="200" resource_id="123" role="admin"/>
+
+ <projects id="100" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission.xml
new file mode 100644
index 00000000000..8f079ec590c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_add_user_permission.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <user_roles id="1" user_id="200" resource_id="123" role="user"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template-result.xml
new file mode 100644
index 00000000000..b70b4ac31a0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template-result.xml
@@ -0,0 +1,36 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+ <!-- new groups permissions : sonar-administrators (admin), sonar-users (user & codeviewer), Anyone (user & codeviewer) -->
+ <group_roles id="3" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="4" group_id="101" resource_id="123" role="user"/>
+ <group_roles id="5" group_id="[null]" resource_id="123" role="user"/>
+ <group_roles id="6" group_id="101" resource_id="123" role="codeviewer"/>
+ <group_roles id="7" group_id="[null]" resource_id="123" role="codeviewer"/>
+ <group_roles id="8" group_id="100" resource_id="123" role="issueadmin"/>
+
+ <!-- new user permission : marius (admin) -->
+ <user_roles id="2" user_id="200" resource_id="123" role="admin"/>
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default_20130101_010203"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="101" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="user"/>
+ <perm_templates_groups id="4" template_id="1" group_id="101" permission_reference="codeviewer"/>
+ <perm_templates_groups id="5" template_id="1" group_id="[null]" permission_reference="codeviewer"/>
+ <perm_templates_groups id="6" template_id="1" group_id="100" permission_reference="issueadmin"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="200" permission_reference="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template.xml
new file mode 100644
index 00000000000..ef9212b8087
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_apply_permission_template.xml
@@ -0,0 +1,31 @@
+<dataset>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!-- on other resources -->
+ <group_roles id="1" group_id="100" resource_id="1" role="admin"/>
+ <group_roles id="2" group_id="101" resource_id="1" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="1" role="admin"/>
+
+
+ <!-- default permission template for all qualifiers -->
+ <permission_templates id="1" name="default" kee="default_20130101_010203"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="100" permission_reference="admin"/>
+ <perm_templates_groups id="2" template_id="1" group_id="101" permission_reference="user"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="user"/>
+ <perm_templates_groups id="4" template_id="1" group_id="101" permission_reference="codeviewer"/>
+ <perm_templates_groups id="5" template_id="1" group_id="[null]" permission_reference="codeviewer"/>
+ <perm_templates_groups id="6" template_id="1" group_id="100" permission_reference="issueadmin"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="200" permission_reference="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_count_component_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_count_component_permissions.xml
new file mode 100644
index 00000000000..aea672478c6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_count_component_permissions.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <groups id="100" name="devs"/>
+
+ <user_roles id="1" user_id="200" resource_id="123" role="user"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="codeviewer"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission-result.xml
new file mode 100644
index 00000000000..e77f558dc36
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <groups id="100" name="devs"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission.xml
new file mode 100644
index 00000000000..9c3bcfce197
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_group_permission.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <groups id="100" name="devs"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="2" group_id="100" resource_id="123" role="user"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission-result.xml
new file mode 100644
index 00000000000..8f079ec590c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <user_roles id="1" user_id="200" resource_id="123" role="user"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission.xml
new file mode 100644
index 00000000000..fe3e01186db
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_delete_user_permission.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <user_roles id="1" user_id="200" resource_id="123" role="user"/>
+ <user_roles id="2" user_id="200" resource_id="123" role="admin"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission-result.xml
new file mode 100644
index 00000000000..276e8d7da3f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <groups id="100" name="devs"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="2" group_id="[null]" resource_id="123" role="user"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission.xml
new file mode 100644
index 00000000000..4552a5339ee
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_anyone_group_permission.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <groups id="100" name="devs"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission-result.xml
new file mode 100644
index 00000000000..9c3bcfce197
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <groups id="100" name="devs"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="2" group_id="100" resource_id="123" role="user"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission.xml
new file mode 100644
index 00000000000..e77f558dc36
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_insert_group_permission.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <groups id="100" name="devs"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+
+ <projects id="123" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="123456789"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions-result.xml
new file mode 100644
index 00000000000..38def462ed1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions-result.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <groups id="100" name="devs"/>
+
+ <user_roles/>
+
+ <group_roles/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions.xml
new file mode 100644
index 00000000000..aea672478c6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionFacadeTest/should_remove_all_permissions.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="200" login="dave.loper" name="Dave Loper" email="dave.loper@company.net" active="[true]"/>
+
+ <groups id="100" name="devs"/>
+
+ <user_roles id="1" user_id="200" resource_id="123" role="user"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="codeviewer"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate-result.xml
new file mode 100644
index 00000000000..c80216743b1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="new_permission"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate.xml
new file mode 100644
index 00000000000..6645e851711
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addGroupPermissionToTemplate.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate-result.xml
new file mode 100644
index 00000000000..7d42701d39f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="[null]" permission_reference="new_permission"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate.xml
new file mode 100644
index 00000000000..6645e851711
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addNullGroupPermissionToTemplate.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate-result.xml
new file mode 100644
index 00000000000..40bf5d45d4e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users id="1" template_id="1" user_id="1" permission_reference="new_permission"/>
+ <perm_templates_groups/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate.xml
new file mode 100644
index 00000000000..6645e851711
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/addUserPermissionToTemplate.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate-result.xml
new file mode 100644
index 00000000000..8596137c817
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate-result.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <permission_templates id="1" name="Môü Gnô Gnèçàß" kee="mou_gno_gneca_20130102_010405" description="my description"
+ key_pattern="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate.xml
new file mode 100644
index 00000000000..5ed00ba028b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createNonAsciiPermissionTemplate.xml
@@ -0,0 +1 @@
+<dataset></dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate-result.xml
new file mode 100644
index 00000000000..4f889d5ba48
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate-result.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_010405" description="my description"
+ key_pattern="myregexp"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate.xml
new file mode 100644
index 00000000000..5ed00ba028b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/createPermissionTemplate.xml
@@ -0,0 +1 @@
+<dataset></dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate-result.xml
new file mode 100644
index 00000000000..115b732cb64
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+ <permission_templates id="2" name="other template" kee="my_template_20130102_030405" description="other description"
+ created_at="[null]" updated_at="[null]"/>
+
+ <perm_templates_users/>
+
+ <perm_templates_groups/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate.xml
new file mode 100644
index 00000000000..0520355af31
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/deletePermissionTemplate.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"
+ created_at="[null]" updated_at="[null]"/>
+ <permission_templates id="2" name="other template" kee="my_template_20130102_030405" description="other description"
+ created_at="[null]" updated_at="[null]"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="1" permission_reference="user_permission1"/>
+ <perm_templates_users id="2" template_id="1" user_id="2" permission_reference="user_permission1"/>
+ <perm_templates_users id="3" template_id="1" user_id="2" permission_reference="user_permission2"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="group_permission1"/>
+ <perm_templates_groups id="2" template_id="1" group_id="2" permission_reference="group_permission1"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="group_permission2"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate-result.xml
new file mode 100644
index 00000000000..140738df7db
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="remaining_permission_other_group"/>
+ <perm_templates_groups id="3" template_id="1" group_id="2" permission_reference="remaining_permission_same_group"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate.xml
new file mode 100644
index 00000000000..3258dcc22e5
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeGroupPermissionFromTemplate.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="remaining_permission_other_group"/>
+ <perm_templates_groups id="2" template_id="1" group_id="2" permission_reference="permission_to_remove"/>
+ <perm_templates_groups id="3" template_id="1" group_id="2" permission_reference="remaining_permission_same_group"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate-result.xml
new file mode 100644
index 00000000000..c37bf10135a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="remaining_permission_other_group"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]"
+ permission_reference="remaining_permission_same_group"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate.xml
new file mode 100644
index 00000000000..9e28fa9e458
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeNullGroupPermissionFromTemplate.xml
@@ -0,0 +1,8 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="remaining_permission_other_group"/>
+ <perm_templates_groups id="2" template_id="1" group_id="[null]" permission_reference="permission_to_remove"/>
+ <perm_templates_groups id="3" template_id="1" group_id="[null]"
+ permission_reference="remaining_permission_same_group"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate-result.xml
new file mode 100644
index 00000000000..89983781b67
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="1" permission_reference="remaining_permission_other_user"/>
+ <perm_templates_users id="3" template_id="1" user_id="2" permission_reference="remaining_permission_same_user"/>
+
+ <perm_templates_groups/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate.xml
new file mode 100644
index 00000000000..779bd770de8
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/removeUserPermissionFromTemplate.xml
@@ -0,0 +1,9 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="1" permission_reference="remaining_permission_other_user"/>
+ <perm_templates_users id="2" template_id="1" user_id="2" permission_reference="permission_to_remove"/>
+ <perm_templates_users id="3" template_id="1" user_id="2" permission_reference="remaining_permission_same_user"/>
+
+ <perm_templates_groups/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group-result.xml
new file mode 100644
index 00000000000..94ab2fde7ed
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="remaining_permission_other_group"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group.xml
new file mode 100644
index 00000000000..2ff5815d855
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/remove_by_group.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"/>
+ <perm_templates_users/>
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="remaining_permission_other_group"/>
+ <perm_templates_groups id="2" template_id="1" group_id="2" permission_reference="permission_to_remove"/>
+ <perm_templates_groups id="3" template_id="1" group_id="2" permission_reference="permission_to_remove2"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectAllPermissionTemplates.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectAllPermissionTemplates.xml
new file mode 100644
index 00000000000..e2e93914e09
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectAllPermissionTemplates.xml
@@ -0,0 +1,8 @@
+<dataset>
+ <permission_templates id="1" name="template1" kee="template1_20130102_030405" description="description1"
+ created_at="[null]" updated_at="[null]"/>
+ <permission_templates id="2" name="template2" kee="template2_20130102_030405" description="description2"
+ created_at="[null]" updated_at="[null]"/>
+ <permission_templates id="3" name="template3" kee="template3_20130102_030405" description="description3"
+ created_at="[null]" updated_at="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectEmptyPermissionTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectEmptyPermissionTemplate.xml
new file mode 100644
index 00000000000..ab25dd40192
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectEmptyPermissionTemplate.xml
@@ -0,0 +1,10 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"
+ created_at="[null]" updated_at="[null]"/>
+
+ <users id="1" login="user1" name="user1"/>
+ <users id="2" login="user2" name="user2"/>
+
+ <groups id="1" name="group1"/>
+ <groups id="2" name="group2"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectPermissionTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectPermissionTemplate.xml
new file mode 100644
index 00000000000..2c82c31400e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/selectPermissionTemplate.xml
@@ -0,0 +1,21 @@
+<dataset>
+ <permission_templates id="1" name="my template" kee="my_template_20130102_030405" description="my description"
+ created_at="[null]" updated_at="[null]"/>
+
+ <perm_templates_users id="1" template_id="1" user_id="1" permission_reference="user_permission1"/>
+ <perm_templates_users id="2" template_id="1" user_id="2" permission_reference="user_permission1"/>
+ <perm_templates_users id="3" template_id="1" user_id="2" permission_reference="user_permission2"/>
+
+ <users id="1" login="login1" name="user1"/>
+ <users id="2" login="login2" name="user2"/>
+
+ <perm_templates_groups id="1" template_id="1" group_id="1" permission_reference="group_permission1"/>
+ <perm_templates_groups id="2" template_id="1" group_id="2" permission_reference="group_permission1"/>
+ <!-- Anyone group -->
+ <perm_templates_groups id="3" template_id="1" group_id="[null]" permission_reference="group_permission2"/>
+ <!-- Unmatched group -->
+ <perm_templates_groups id="4" template_id="1" group_id="999999" permission_reference="group_permission2"/>
+
+ <groups id="1" name="group1"/>
+ <groups id="2" name="group2"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate-result.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate-result.xml
new file mode 100644
index 00000000000..2449862a0a8
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <permission_templates id="1" name="new_name" kee="template1_20130102_030405" description="new_description"
+ key_pattern="new_regexp" created_at="[null]" updated_at="[null]"/>
+ <permission_templates id="2" name="template2" kee="template2_20130102_030405" description="description2"
+ key_pattern="barregexp" created_at="[null]" updated_at="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate.xml b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate.xml
new file mode 100644
index 00000000000..c14e8b61e8b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/PermissionTemplateDaoTest/updatePermissionTemplate.xml
@@ -0,0 +1,6 @@
+<dataset>
+ <permission_templates id="1" name="template1" kee="template1_20130102_030405" description="description1"
+ key_pattern="fooregexp" created_at="[null]" updated_at="[null]"/>
+ <permission_templates id="2" name="template2" kee="template2_20130102_030405" description="description2"
+ key_pattern="barregexp" created_at="[null]" updated_at="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/select_only_enable_users.xml b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/select_only_enable_users.xml
new file mode 100644
index 00000000000..662af1a0a8c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/select_only_enable_users.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <users id="200" login="user1" name="User1" active="[true]"/>
+ <users id="201" login="user2" name="User2" active="[true]"/>
+ <users id="202" login="user3" name="User3" active="[true]"/>
+ <users id="999" login="disabledUser" name="disabledUser" active="[false]"/>
+
+ <!-- Project permissions -->
+ <user_roles id="1" user_id="200" resource_id="100" role="user"/>
+ <user_roles id="2" user_id="200" resource_id="100" role="admin"/>
+ <user_roles id="3" user_id="200" resource_id="100" role="codeviewer"/>
+
+ <user_roles id="4" user_id="201" resource_id="100" role="user"/>
+
+ <!-- Global permission -->
+ <user_roles id="10" user_id="200" resource_id="[null]" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions.xml
new file mode 100644
index 00000000000..6f40d31f21d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions.xml
@@ -0,0 +1,17 @@
+<dataset>
+
+ <users id="200" login="user1" name="User1" active="[true]"/>
+ <users id="201" login="user2" name="User2" active="[true]"/>
+ <users id="202" login="user3" name="User3" active="[true]"/>
+
+ <!-- Project permissions -->
+ <user_roles id="1" user_id="200" resource_id="100" role="user"/>
+ <user_roles id="2" user_id="200" resource_id="100" role="admin"/>
+ <user_roles id="3" user_id="200" resource_id="100" role="codeviewer"/>
+
+ <user_roles id="4" user_id="201" resource_id="100" role="user"/>
+
+ <!-- Global permission -->
+ <user_roles id="10" user_id="200" resource_id="[null]" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml
new file mode 100644
index 00000000000..6b39124ce6c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="200" login="user3" name="User3" active="[true]"/>
+ <users id="201" login="user1" name="User1" active="[true]"/>
+ <users id="202" login="user2" name="User2" active="[true]"/>
+
+ <user_roles id="1" user_id="200" resource_id="100" role="user"/>
+ <user_roles id="2" user_id="200" resource_id="100" role="admin"/>
+ <user_roles id="3" user_id="200" resource_id="100" role="codeviewer"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml
new file mode 100644
index 00000000000..14833919475
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <users id="200" login="user1" name="User1" active="[true]"/>
+ <users id="201" login="user2" name="User2" active="[true]"/>
+ <users id="202" login="user3" name="User3" active="[true]"/>
+ <users id="999" login="disabledUser" name="disabledUser" active="[false]"/>
+
+ <perm_templates_users id="1" user_id="200" permission_reference="user" template_id="50"/>
+ <perm_templates_users id="2" user_id="200" permission_reference="admin" template_id="50"/>
+ <perm_templates_users id="3" user_id="200" permission_reference="codeviewer" template_id="50"/>
+
+ <perm_templates_users id="4" user_id="201" permission_reference="user" template_id="50"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions.xml
new file mode 100644
index 00000000000..e886636bd9b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <users id="200" login="user1" name="User1" active="[true]"/>
+ <users id="201" login="user2" name="User2" active="[true]"/>
+ <users id="202" login="user3" name="User3" active="[true]"/>
+
+ <perm_templates_users id="1" user_id="200" permission_reference="user" template_id="50"/>
+ <perm_templates_users id="2" user_id="200" permission_reference="admin" template_id="50"/>
+ <perm_templates_users id="3" user_id="200" permission_reference="codeviewer" template_id="50"/>
+
+ <perm_templates_users id="4" user_id="201" permission_reference="user" template_id="50"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml
new file mode 100644
index 00000000000..fd8b981c203
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/permission/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <users id="200" login="user3" name="User3" active="[true]"/>
+ <users id="201" login="user1" name="User1" active="[true]"/>
+ <users id="202" login="user2" name="User2" active="[true]"/>
+
+ <perm_templates_users id="1" user_id="200" permission_reference="user" template_id="50"/>
+ <perm_templates_users id="2" user_id="200" permission_reference="admin" template_id="50"/>
+ <perm_templates_users id="3" user_id="200" permission_reference="codeviewer" template_id="50"/>
+
+ <perm_templates_users id="4" user_id="201" permission_reference="user" template_id="50"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml
new file mode 100644
index 00000000000..ba4619fbedd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties-result.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <!-- global -->
+ <!-- <properties id="1" prop_key="to_be_deleted" text_value="new_global" resource_id="[null]" user_id="[null]"/> -->
+ <properties id="2" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <!-- <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/> -->
+ <properties id="4" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <!-- <properties id="5" prop_key="to_be_deleted" text_value="new_user" resource_id="[null]" user_id="100"/> -->
+ <properties id="6" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml
new file mode 100644
index 00000000000..0953b0ecd9f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteAllProperties.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="to_be_deleted" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/>
+ <properties id="4" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="to_be_deleted" text_value="new_user" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml
new file mode 100644
index 00000000000..a5cfed3378b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <!-- global -->
+ <!--<properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>-->
+
+ <!-- project -->
+ <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml
new file mode 100644
index 00000000000..3e5eb87705c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperties.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml
new file mode 100644
index 00000000000..0428139feb6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+ <!--<properties id="2" prop_key="to_be_deleted" text_value="xxx" resource_id="[null]" user_id="[null]"/>-->
+
+ <!-- project -->
+ <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="4" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml
new file mode 100644
index 00000000000..aaf0fd642d3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/deleteGlobalProperty.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="to_be_deleted" text_value="xxx" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project - do not delete this project property that has the same key -->
+ <properties id="3" prop_key="to_be_deleted" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="4" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml
new file mode 100644
index 00000000000..97b8b6f02b3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <!--<properties id="1" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="1" user_id="[null]"/>-->
+ <!--<properties id="2" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="2" user_id="[null]"/>-->
+
+ <properties id="3" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+
+ <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
+ <properties id="5" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
+ <properties id="6" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml
new file mode 100644
index 00000000000..4e07f27e283
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_properties.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <properties id="1" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
+ <properties id="2" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
+
+ <properties id="3" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+
+ <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
+ <properties id="5" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
+ <properties id="6" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml
new file mode 100644
index 00000000000..0b889b14bb3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property-result.xml
@@ -0,0 +1,22 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- struts -->
+ <!--<properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>-->
+
+ <!-- commons -->
+ <properties id="4" prop_key="commonslang.one" text_value="two" resource_id="11" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.two" text_value="two" resource_id="10" user_id="100"/>
+
+ <properties id="7" prop_key="commonslang.one" text_value="one" resource_id="12" user_id="[null]"/>
+
+ <projects id="10" uuid="A" kee="org.struts:struts"/>
+ <projects id="11" uuid="B" kee="org.apache:commons-lang"/>
+ <projects id="12" uuid="C" kee="other"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml
new file mode 100644
index 00000000000..99bd75917c1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/delete_project_property.xml
@@ -0,0 +1,22 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- struts -->
+ <properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>
+
+ <!-- commons -->
+ <properties id="4" prop_key="commonslang.one" text_value="two" resource_id="11" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.two" text_value="two" resource_id="10" user_id="100"/>
+
+ <properties id="7" prop_key="commonslang.one" text_value="one" resource_id="12" user_id="[null]"/>
+
+ <projects id="10" uuid="A" kee="org.struts:struts"/>
+ <projects id="11" uuid="B" kee="org.apache:commons-lang"/>
+ <projects id="12" uuid="C" kee="other"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/findNotificationSubscribers.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/findNotificationSubscribers.xml
new file mode 100644
index 00000000000..9bfd1dc3001
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/findNotificationSubscribers.xml
@@ -0,0 +1,55 @@
+<dataset>
+
+ <users
+ id="1"
+ login="eric"
+ />
+
+ <users
+ id="2"
+ login="simon"
+ />
+
+ <projects id="42" uuid="PROJECT_A" kee="org.apache:struts"/>
+
+ <!-- global subscription -->
+ <properties
+ id="1"
+ prop_key="notification.DispatcherWithGlobalSubscribers.Email"
+ text_value="true"
+ resource_id="[null]"
+ user_id="2"/>
+
+ <!-- project subscription -->
+ <properties
+ id="2"
+ prop_key="notification.DispatcherWithProjectSubscribers.Email"
+ text_value="true"
+ resource_id="42"
+ user_id="1"/>
+
+ <!-- project subscription -->
+ <properties
+ id="3"
+ prop_key="notification.DispatcherWithGlobalAndProjectSubscribers.Email"
+ text_value="true"
+ resource_id="56"
+ user_id="1"/>
+
+ <!-- project subscription -->
+ <properties
+ id="4"
+ prop_key="notification.DispatcherWithGlobalAndProjectSubscribers.Email"
+ text_value="true"
+ resource_id="42"
+ user_id="1"/>
+
+ <!-- global subscription -->
+ <properties
+ id="5"
+ prop_key="notification.DispatcherWithGlobalAndProjectSubscribers.Email"
+ text_value="true"
+ resource_id="[null]"
+ user_id="2"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml
new file mode 100644
index 00000000000..3e5eb87705c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml
new file mode 100644
index 00000000000..5ed00ba028b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insert.xml
@@ -0,0 +1 @@
+<dataset></dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml
new file mode 100644
index 00000000000..5594180c370
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <properties prop_key="to_be_inserted" text_value="inserted" resource_id="[null]" user_id="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/insertGlobalProperties.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml
new file mode 100644
index 00000000000..0877b0035fe
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="sonar.license" text_value="the license" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml
new file mode 100644
index 00000000000..6ab761e2c6f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/renamePropertyKey.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="sonar.license.secured" text_value="the license" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectGlobalProperties.xml
new file mode 100644
index 00000000000..e5aa737e9a0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectGlobalProperties.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <properties id="3" prop_key="project.one" text_value="one" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="4" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="5" prop_key="user.two" text_value="two" resource_id="10" user_id="100"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectProperties.xml
new file mode 100644
index 00000000000..99bd75917c1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectProperties.xml
@@ -0,0 +1,22 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- struts -->
+ <properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>
+
+ <!-- commons -->
+ <properties id="4" prop_key="commonslang.one" text_value="two" resource_id="11" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.two" text_value="two" resource_id="10" user_id="100"/>
+
+ <properties id="7" prop_key="commonslang.one" text_value="one" resource_id="12" user_id="[null]"/>
+
+ <projects id="10" uuid="A" kee="org.struts:struts"/>
+ <projects id="11" uuid="B" kee="org.apache:commons-lang"/>
+ <projects id="12" uuid="C" kee="other"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml
new file mode 100644
index 00000000000..52fec852a8d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/selectProjectPropertiesByResourceId.xml
@@ -0,0 +1,22 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- struts -->
+ <properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>
+
+ <!-- commons -->
+ <properties id="4" prop_key="commonslang.one" text_value="two" resource_id="11" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.two" text_value="two" resource_id="10" user_id="[null]"/>
+
+ <properties id="7" prop_key="commonslang.one" text_value="one" resource_id="12" user_id="[null]"/>
+
+ <projects id="10" uuid="A" kee="org.struts:struts"/>
+ <projects id="11" uuid="B" kee="org.apache:commons-lang"/>
+ <projects id="12" uuid="C" kee="other"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml
new file mode 100644
index 00000000000..2873d69a5bc
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_by_query.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- struts -->
+ <properties id="3" prop_key="struts.one" text_value="one" resource_id="10" user_id="[null]"/>
+
+ <!-- commons -->
+ <properties id="4" prop_key="commonslang.one" text_value="two" resource_id="11" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.two" text_value="two" resource_id="10" user_id="100"/>
+
+ <properties id="7" prop_key="commonslang.one" text_value="one" resource_id="12" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_module_properties_tree.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_module_properties_tree.xml
new file mode 100644
index 00000000000..938910a0e01
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/select_module_properties_tree.xml
@@ -0,0 +1,61 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.one" text_value="one" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="global.two" text_value="two" resource_id="[null]" user_id="[null]"/>
+
+ <!-- org.struts:struts -->
+ <properties id="3" prop_key="struts.one" text_value="one" resource_id="1" user_id="[null]"/>
+
+ <!-- org.struts:struts-core -->
+ <properties id="4" prop_key="core.one" text_value="one" resource_id="2" user_id="[null]"/>
+ <properties id="7" prop_key="core.two" text_value="two" resource_id="2" user_id="[null]"/>
+
+ <!-- org.struts:struts-data -->
+ <properties id="8" prop_key="data.one" text_value="one" resource_id="3" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="5" prop_key="user.one" text_value="one" resource_id="[null]" user_id="100"/>
+ <properties id="6" prop_key="user.two" text_value="two" resource_id="1" user_id="102"/>
+
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="."
+ description="the description" long_name="Apache Struts"
+ enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- module -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
+ uuid="EFGH" project_uuid="ABCD" module_uuid="[null]" module_uuid_path=".ABCD."
+ scope="PRJ" qualifier="BRC" long_name="Struts Core"
+ description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- sub module -->
+ <projects id="3" root_id="1" kee="org.struts:struts-data" name="Struts Data"
+ uuid="FGHI" project_uuid="ABCD" module_uuid="EFGH" module_uuid_path=".ABCD.EFGH."
+ scope="PRJ" qualifier="BRC" long_name="Struts Data"
+ description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="4" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts"
+ uuid="GHIJ" project_uuid="ABCD" module_uuid="FGHI" module_uuid_path=".ABCD.EFGH.FGHI."
+ name="src/org/struts" root_id="3"
+ description="[null]"
+ enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/org/struts"
+ authorization_updated_at="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="5" scope="FIL" qualifier="FIL"
+ kee="org.struts:struts-core:src/org/struts/RequestContext.java"
+ uuid="HIJK" project_uuid="ABCD" module_uuid="GHIJ" module_uuid_path=".ABCD.EFGH.FGHI."
+ name="RequestContext.java" root_id="3"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"
+ path="src/org/struts/RequestContext.java" authorization_updated_at="[null]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/shouldFindUsersForNotification.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/shouldFindUsersForNotification.xml
new file mode 100644
index 00000000000..891d895d7f2
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/shouldFindUsersForNotification.xml
@@ -0,0 +1,46 @@
+<dataset>
+
+ <projects id="45" uuid="uuid_45"/>
+ <projects id="56" uuid="uuid_56"/>
+
+ <properties
+ id="1"
+ prop_key="notification.NewViolations.Email"
+ text_value="true"
+ resource_id="45"
+ user_id="2"/>
+
+ <properties
+ id="2"
+ prop_key="notification.NewViolations.Twitter"
+ text_value="true"
+ resource_id="[null]"
+ user_id="3"/>
+
+ <properties
+ id="3"
+ prop_key="notification.NewViolations.Twitter"
+ text_value="true"
+ resource_id="56"
+ user_id="1"/>
+
+ <properties
+ id="4"
+ prop_key="notification.NewViolations.Twitter"
+ text_value="true"
+ resource_id="56"
+ user_id="3"/>
+
+ <users
+ id="1"
+ login="user1"/>
+
+ <users
+ id="2"
+ login="user2"/>
+
+ <users
+ id="3"
+ login="user3"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml
new file mode 100644
index 00000000000..b4fc11a8cdb
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml
new file mode 100644
index 00000000000..b4fc11a8cdb
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/should_not_rename_if_same_key.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <properties id="1" prop_key="foo" text_value="bar" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml
new file mode 100644
index 00000000000..c3268596de3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update-result.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.key" text_value="new_global" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <properties id="2" prop_key="project.key" text_value="new_project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="3" prop_key="user.key" text_value="new_user" resource_id="[null]" user_id="100"/>
+
+ <!-- null value -->
+ <properties id="4" prop_key="null.value" text_value="[null]" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml
new file mode 100644
index 00000000000..5229a2b791a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/update.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <!-- global -->
+ <properties id="1" prop_key="global.key" text_value="global" resource_id="[null]" user_id="[null]"/>
+
+ <!-- project -->
+ <properties id="2" prop_key="project.key" text_value="project" resource_id="10" user_id="[null]"/>
+
+ <!-- user -->
+ <properties id="3" prop_key="user.key" text_value="user" resource_id="[null]" user_id="100"/>
+
+ <!-- null value -->
+ <properties id="4" prop_key="null.value" text_value="not null" resource_id="[null]" user_id="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml
new file mode 100644
index 00000000000..12033fdc629
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <properties prop_key="to_be_updated" text_value="updated" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml
new file mode 100644
index 00000000000..4616072f3d9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updateGlobalProperties.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <properties id="1" prop_key="to_be_updated" text_value="old value" resource_id="[null]" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml
new file mode 100644
index 00000000000..194761a6b2d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue-result.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <properties id="1" prop_key="sonar.profile.java" text_value="Default" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="sonar.profile.java" text_value="Default" resource_id="1" user_id="[null]"/>
+
+ <properties id="3" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+ <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml
new file mode 100644
index 00000000000..5567744e146
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/property/PropertiesDaoTest/updatePropertiesFromKeyAndValueToNewValue.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <properties id="1" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+ <properties id="2" prop_key="sonar.profile.java" text_value="Sonar Way" resource_id="1" user_id="[null]"/>
+
+ <properties id="3" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="[null]" user_id="[null]"/>
+ <properties id="4" prop_key="sonar.profile.js" text_value="Sonar Way" resource_id="2" user_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteResource.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteResource.xml
new file mode 100644
index 00000000000..02a88004d9b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteResource.xml
@@ -0,0 +1,38 @@
+<dataset>
+
+ <projects id="1" uuid="1" enabled="[true]" root_id="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <events id="1" name="Version 1.0" component_uuid="1" snapshot_id="1" category="VERSION" description="[null]"
+ event_date="1228222680000" created_at="1228222680000" event_data="[null]"/>
+
+ <issues id="1" kee="ABCDE" component_uuid="1" project_uuid="1" status="CLOSED" resolution="[null]" line="200"
+ severity="BLOCKER"
+ reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ created_at="[null]"
+ updated_at="[null]"
+ />
+
+ <issue_changes id="1" kee="ABDA" issue_key="ABCDE" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="this is a comment"/>
+
+ <authors id="1" person_id="1" login="tartanpion" created_at="[null]" updated_at="[null]"/>
+ <authors id="2" person_id="1" login="fanfoue" created_at="[null]" updated_at="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml
new file mode 100644
index 00000000000..f17226a159a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot-result.xml
@@ -0,0 +1,29 @@
+<dataset>
+
+ <!-- snapshot to keep -->
+ <snapshots id="1" purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]"
+ period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="[false]"
+ path="[null]"/>
+
+ <project_measures ID="1" characteristic_id="[null]" url="[null]" variation_value_1="[null]" variation_value_2="[null]"
+ variation_value_3="[null]" variation_value_4="[null]"
+ variation_value_5="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="1" rules_category_id="[null]"
+ RULE_ID="1"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" measure_data="[null]"/>
+ <events id="1" name="Version 1.0" component_uuid="1" snapshot_id="1" category="VERSION" description="[null]"
+ event_date="1228222680000" created_at="1228222680000" event_data="[null]"/>
+ <duplications_index id="1" project_snapshot_id="1" snapshot_id="1" hash="bb" index_in_file="0" start_line="0"
+ end_line="0"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml
new file mode 100644
index 00000000000..3f756fc37d3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteSnapshot.xml
@@ -0,0 +1,57 @@
+<dataset>
+
+ <!-- snapshot to keep -->
+ <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <project_measures ID="1" characteristic_id="[null]" url="[null]" variation_value_1="[null]" variation_value_2="[null]"
+ variation_value_3="[null]" variation_value_4="[null]"
+ variation_value_5="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="1" rules_category_id="[null]"
+ RULE_ID="1"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" measure_data="[null]"/>
+ <events id="1" name="Version 1.0" component_uuid="1" snapshot_id="1" category="VERSION" description="[null]"
+ event_date="1228222680000" created_at="1228222680000" event_data="[null]"/>
+ <duplications_index id="1" project_snapshot_id="1" snapshot_id="1" hash="bb" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+
+ <!-- snapshot to remove, id 5 on resource 5-->
+ <snapshots id="5" project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <project_measures ID="2" characteristic_id="[null]" url="[null]" variation_value_1="[null]" variation_value_2="[null]"
+ variation_value_3="[null]" variation_value_4="[null]"
+ variation_value_5="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="5" rules_category_id="[null]"
+ RULE_ID="1"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" measure_data="[null]"/>
+ <events id="2" name="Version 1.0" component_uuid="5" snapshot_id="5" category="VERSION" description="[null]"
+ event_date="1228222680000" created_at="1228222680000" event_data="[null]"/>
+ <duplications_index id="2" project_snapshot_id="5" snapshot_id="5" hash="bb" index_in_file="0" start_line="0"
+ end_line="0"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot-result.xml
new file mode 100644
index 00000000000..c59e34d5c8b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot-result.xml
@@ -0,0 +1,87 @@
+<dataset>
+
+ <metrics id="1" delete_historical_data="[null]"
+ name="ncloc" val_type="INT" description="[null]" domain="[null]" short_name="" enabled="[true]"
+ worst_value="[null]" optimized_best_value="[null]" best_value="[null]"
+ direction="0" hidden="[false]"/>
+
+ <metrics id="2" delete_historical_data="[true]"
+ name="coverage" val_type="INT" description="[null]" domain="[null]" short_name="" enabled="[true]"
+ worst_value="0" optimized_best_value="[true]" best_value="100"
+ direction="1" hidden="[false]"/>
+
+ <characteristics id="1" kee="M1C1" name="M1C1" parent_id="[null]" characteristic_order="1" enabled="[true]"/>
+ <characteristics id="2" kee="M1C2" name="M1C1" parent_id="1" characteristic_order="[null]" enabled="[true]"/>
+ <characteristics id="3" kee="M1C3" name="M1C3" parent_id="2" characteristic_order="[null]" enabled="[true]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!-- do not delete standard measure -->
+ <project_measures id="1" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="[null]" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- delete measure on rule -->
+ <!--<project_measures ID="2" project_id="1" SNAPSHOT_ID="1" RULE_ID="33" characteristic_id="[null]" METRIC_ID="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" VALUE="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>-->
+
+ <!-- do not delete measure on characteristic -->
+ <project_measures id="3" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="1" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- do not delete measure on characteristic -->
+ <project_measures id="4" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="2" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- delete measure on metrics that are flagged with delete_historical_data=true -->
+ <!--<project_measures ID="6" project_id="1" SNAPSHOT_ID="1" RULE_ID="[null]" characteristic_id="[null]" METRIC_ID="2"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" VALUE="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>-->
+
+ <!-- delete measure on developers -->
+ <!--<project_measures id="7" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="[null]" metric_id="2"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ person_id="123456"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>-->
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot.xml
new file mode 100644
index 00000000000..d4931b9a343
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldDeleteWastedMeasuresWhenPurgingSnapshot.xml
@@ -0,0 +1,87 @@
+<dataset>
+
+ <metrics id="1" delete_historical_data="[null]"
+ name="ncloc" val_type="INT" description="[null]" domain="[null]" short_name="" enabled="[true]"
+ worst_value="[null]" optimized_best_value="[null]" best_value="[null]"
+ direction="0" hidden="[false]"/>
+
+ <metrics id="2" delete_historical_data="[true]"
+ name="coverage" val_type="INT" description="[null]" domain="[null]" short_name="" enabled="[true]"
+ worst_value="0" optimized_best_value="[true]" best_value="100"
+ direction="1" hidden="[false]"/>
+
+ <characteristics id="1" kee="M1C1" name="M1C1" parent_id="[null]" characteristic_order="1" enabled="[true]"/>
+ <characteristics id="2" kee="M1C2" name="M1C1" parent_id="1" characteristic_order="[null]" enabled="[true]"/>
+ <characteristics id="3" kee="M1C3" name="M1C3" parent_id="2" characteristic_order="[null]" enabled="[true]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!-- do not delete standard measure -->
+ <project_measures id="1" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="[null]" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- delete measure on rule -->
+ <project_measures id="2" project_id="1" snapshot_id="1" rule_id="33" characteristic_id="[null]" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- do not delete measure on root characteristic -->
+ <project_measures id="3" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="1" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- do not delete measure on characteristic -->
+ <project_measures id="4" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="2" metric_id="1"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- delete measure on metrics that are flagged with delete_historical_data=true -->
+ <project_measures id="6" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="[null]" metric_id="2"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <!-- delete measure on developers -->
+ <project_measures id="7" project_id="1" snapshot_id="1" rule_id="[null]" characteristic_id="[null]" metric_id="2"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ person_id="123456"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" value="10.0"
+ rules_category_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml
new file mode 100644
index 00000000000..370f4d3ab34
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot-result.xml
@@ -0,0 +1,75 @@
+<!--
+
+Changes:
+* snapshot.purge_status=1
+* commented-out rows are deleted
+
+Note that measures, events and reviews are not deleted.
+
+-->
+<dataset>
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!--switched_off="[null]" permanent_id="[null]" RULE_ID="1" FAILURE_LEVEL="2"-->
+ <!--MESSAGE="msg1" LINE="[null]" COST="[null]"-->
+ <!--created_at="2008-12-02 13:58:00.00"-->
+ <!--checksum="[null]" person_id="[null]"/>-->
+
+ <project_measures ID="1" project_id="1" SNAPSHOT_ID="1" RULE_ID="[null]" characteristic_id="[null]"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" VALUE="10.0" METRIC_ID="1"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <events id="1" component_uuid="1" snapshot_id="1"
+ category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000"
+ created_at="1228222680000"
+ event_data="[null]"/>
+
+ <!--<duplications_index id="1" project_snapshot_id="1" snapshot_id="1"-->
+ <!--hash="bb" index_in_file="0" start_line="0" end_line="0"/>-->
+
+
+ <!-- The following is not purged but is kept for DBUnit -->
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="[null]" root_project_id="2" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+
+ <project_measures ID="2" project_id="2" SNAPSHOT_ID="2" RULE_ID="[null]" characteristic_id="[null]"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" VALUE="10.0" METRIC_ID="1"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <events id="2" component_uuid="2" snapshot_id="2"
+ category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000"
+ created_at="1228222680000"
+ event_data="[null]"/>
+
+ <duplications_index id="2" project_snapshot_id="2" snapshot_id="2"
+ hash="bb" index_in_file="0" start_line="0" end_line="0"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml
new file mode 100644
index 00000000000..53144869569
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeCommandsTest/shouldPurgeSnapshot.xml
@@ -0,0 +1,59 @@
+<dataset>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <project_measures ID="1" project_id="1" SNAPSHOT_ID="1" RULE_ID="[null]" characteristic_id="[null]"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]" variation_value_5="[null]" rule_priority="[null]" alert_text="[null]"
+ VALUE="10.0" METRIC_ID="1" rules_category_id="[null]" person_id="[null]" text_value="[null]"
+ tendency="[null]" measure_date="[null]" alert_status="[null]" description="[null]"
+ measure_data="[null]"/>
+
+ <events id="1" component_uuid="1" snapshot_id="1"
+ category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000"
+ created_at="1228222680000"
+ event_data="[null]"/>
+
+ <duplications_index id="1" project_snapshot_id="1" snapshot_id="1"
+ hash="bb" index_in_file="0" start_line="0" end_line="0"/>
+
+
+ <!-- The following is not purged but is kept for DBUnit -->
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="[null]" root_project_id="2" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <project_measures ID="2" project_id="2" SNAPSHOT_ID="2" RULE_ID="[null]" characteristic_id="[null]"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]"
+ variation_value_5="[null]" rule_priority="[null]" alert_text="[null]" VALUE="10.0" METRIC_ID="1"
+ rules_category_id="[null]"
+ person_id="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" alert_status="[null]"
+ description="[null]" measure_data="[null]"/>
+
+ <events id="2" component_uuid="2" snapshot_id="2"
+ category="VERSION" description="[null]" name="Version 1.0" event_date="1228222680000"
+ created_at="1228222680000"
+ event_data="[null]"/>
+
+ <duplications_index id="2" project_snapshot_id="2" snapshot_id="2"
+ hash="bb" index_in_file="0" start_line="0" end_line="0"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources-result.xml
new file mode 100644
index 00000000000..2cb6b58826f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources-result.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <file_sources id="2" project_uuid="ABCD" file_uuid="KLMN" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654988"
+ created_at="123456789" updated_at="123456789" src_hash="123456" data_type="SOURCE"/>
+ <file_sources id="4" project_uuid="ABCD" file_uuid="KLMN" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654988"
+ created_at="123456789" updated_at="123456789" src_hash="123456" data_type="TEST"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources.xml
new file mode 100644
index 00000000000..5307fc8219a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/delete_file_sources_of_disabled_resources.xml
@@ -0,0 +1,87 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the directory -->
+ <projects id="2" enabled="[true]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the files -->
+ <projects id="3" enabled="[true]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+ <projects id="4" enabled="[true]" root_id="1" uuid="KLMN" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/DeletedFile.java"
+ name="my/dir/DeletedFile.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <snapshots id="3"
+ project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!-- isLast is true, don't want to delete associated source lines -->
+ <snapshots id="4"
+ project_id="4" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <file_sources id="1" project_uuid="ABCD" file_uuid="GHIJ" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654987"
+ created_at="123456789" updated_at="123456789" src_hash="12345" data_type="SOURCE"/>
+ <file_sources id="2" project_uuid="ABCD" file_uuid="KLMN" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654988"
+ created_at="123456789" updated_at="123456789" src_hash="123456" data_type="SOURCE"/>
+ <file_sources id="3" project_uuid="ABCD" file_uuid="GHIJ" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654987"
+ created_at="123456789" updated_at="123456789" src_hash="12345" data_type="TEST"/>
+ <file_sources id="4" project_uuid="ABCD" file_uuid="KLMN" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654988"
+ created_at="123456789" updated_at="123456789" src_hash="123456" data_type="TEST"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot-result.xml
new file mode 100644
index 00000000000..530dafeedaa
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot-result.xml
@@ -0,0 +1,118 @@
+<!--
+
+What has been changed :
+* enabled=false on projects
+* purge_status=1 on snapshots
+* resolve not already resolved issues on all components
+
+-->
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[false]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the directory -->
+ <projects id="2" enabled="[false]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path="."
+ created_at="[null]"
+ long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the file -->
+ <projects id="3" enabled="[false]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+
+ <snapshots id="3"
+ project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!-- Open issue on file -->
+ <issues id="1" kee="ISSUE-1"
+ component_uuid="GHIJ"
+ project_uuid="ABCD"
+ status="CLOSED"
+ issue_close_date="1396994400000"
+ resolution="REMOVED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="1450000000000" issue_creation_date="1366063200000" issue_update_date="1396994400000"
+ created_at="1450000000000" tags="[null]"/>
+
+ <!-- Open issue on directory -->
+ <issues id="2" kee="ISSUE-2"
+ component_uuid="EFGH"
+ project_uuid="ABCD"
+ status="CLOSED"
+ issue_close_date="1396994400000"
+ resolution="REMOVED" line="[null]" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="1450000000000" issue_creation_date="1366063200000" issue_update_date="1396994400000"
+ created_at="1450000000000" tags="[null]"/>
+
+ <!-- Open issue on project -->
+ <issues id="3" kee="ISSUE-3"
+ component_uuid="ABCD"
+ project_uuid="ABCD"
+ status="CLOSED"
+ issue_close_date="1396994400000"
+ resolution="REMOVED" line="[null]" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="1450000000000" issue_creation_date="1366063200000" issue_update_date="1396994400000"
+ created_at="1450000000000" tags="[null]"/>
+
+ <!-- Resolved issue on file -> not to be updated -->
+ <issues id="4" kee="ISSUE-4"
+ component_uuid="GHIJ"
+ project_uuid="ABCD"
+ status="CLOSED"
+ issue_close_date="1449529200000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="1450000000000" issue_creation_date="1366063200000" issue_update_date="1396908000000"
+ created_at="1450000000000" tags="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot.xml
new file mode 100644
index 00000000000..0cfef24e585
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/disable_resources_without_last_snapshot.xml
@@ -0,0 +1,110 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the directory -->
+ <projects id="2" enabled="[true]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path="."
+ created_at="[null]"
+ long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the file -->
+ <projects id="3" enabled="[true]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <snapshots id="3"
+ project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- Open issue on file -->
+ <issues id="1" kee="ISSUE-1"
+ component_uuid="GHIJ"
+ project_uuid="ABCD"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1450000000000"/>
+
+ <!-- Open issue on directory -->
+ <issues id="2" kee="ISSUE-2"
+ component_uuid="EFGH"
+ project_uuid="ABCD"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="[null]" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1450000000000"/>
+
+ <!-- Open issue on project -->
+ <issues id="3" kee="ISSUE-3"
+ component_uuid="ABCD"
+ project_uuid="ABCD"
+ status="CONFIRM"
+ issue_close_date="[null]"
+ resolution="[null]" line="[null]" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1450000000000"/>
+
+ <!-- Resolved issue on file -> not to be updated -->
+ <issues id="4" kee="ISSUE-4"
+ component_uuid="GHIJ"
+ project_uuid="ABCD"
+ status="CLOSED"
+ issue_close_date="1449529200000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="1450000000000" issue_creation_date="1366063200000" issue_update_date="1396908000000"
+ created_at="1450000000000"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/select_purgeable_file_uuids.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/select_purgeable_file_uuids.xml
new file mode 100644
index 00000000000..b3bca4514fb
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/select_purgeable_file_uuids.xml
@@ -0,0 +1,88 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the directory -->
+ <projects id="2" enabled="[true]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the files -->
+ <projects id="3" enabled="[true]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+ <projects id="4" enabled="[true]" root_id="1" uuid="KLMN" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java"
+ name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+ <!-- the file has already been disabled. It should not be selected -->
+ <projects id="5" enabled="[false]" root_id="1" uuid="OPQR" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java"
+ name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!-- isLast is false -->
+ <snapshots id="3"
+ project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <snapshots id="4"
+ project_id="4" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <file_sources id="1" project_uuid="ABCD" file_uuid="GHIJ" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654987"
+ created_at="123456789" updated_at="123456789" data_type="SOURCE"/>
+ <file_sources id="2" project_uuid="ABCD" file_uuid="KLMN" binary_data="[null]" line_hashes="[null]"
+ data_hash="321654988"
+ created_at="123456789" updated_at="123456789" data_type="SOURCE"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds-result.xml
new file mode 100644
index 00000000000..041565fc171
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds-result.xml
@@ -0,0 +1,49 @@
+<!--
+
+Snapshot 2 has been deleted
+
+-->
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- past snapshot with status "processed" and already purged -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshot with status "unprocessed" -> to be deleted -->
+ <!--<snapshots id="2"-->
+ <!--project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"-->
+ <!--status="U" islast="[false]" purge_status="0"-->
+ <!--period1_mode="[null]" period1_param="[null]" period1_date="[null]"-->
+ <!--period2_mode="[null]" period2_param="[null]" period2_date="[null]"-->
+ <!--period3_mode="[null]" period3_param="[null]" period3_date="[null]"-->
+ <!--period4_mode="[null]" period4_param="[null]" period4_date="[null]"-->
+ <!--period5_mode="[null]" period5_param="[null]" period5_date="[null]"-->
+ <!--depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000" version="[null]" path="[null]"/>-->
+
+ <!-- snapshot with status "processed" and flagged as "last" -> do not purge and do not delete -->
+ <snapshots id="3"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="0"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds.xml
new file mode 100644
index 00000000000..0c821011796
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteAbortedBuilds.xml
@@ -0,0 +1,45 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <!-- past snapshot with status "processed" and already purged -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshot with status "unprocessed" -> to be deleted -->
+ <snapshots id="2"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="U" islast="[false]" purge_status="0"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshot with status "processed" and flagged as "last" -> do not purge and do not delete -->
+ <snapshots id="3"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="0"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles-result.xml
new file mode 100644
index 00000000000..a43778f76a0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles-result.xml
@@ -0,0 +1,98 @@
+<!--
+
+What has been changed : purge_status=1 on snapshot 4 (PRJ) and snapshots 5 and 6 (DIR/FIL) are deleted
+
+-->
+
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the directory -->
+ <projects id="2" enabled="[true]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path="."
+ created_at="[null]"
+ long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the file -->
+ <projects id="3" enabled="[true]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- do not purge last snapshots -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="DIR" qualifier="DIR" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <snapshots id="3"
+ project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="FIL" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshots to be purged -->
+ <snapshots id="4"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!--<snapshots id="5"-->
+ <!--project_id="2" parent_snapshot_id="4" root_project_id="1" root_snapshot_id="4"-->
+ <!--status="P" islast="[false]" purge_status="1"-->
+ <!--period1_mode="[null]" period1_param="[null]" period1_date="[null]"-->
+ <!--period2_mode="[null]" period2_param="[null]" period2_date="[null]"-->
+ <!--period3_mode="[null]" period3_param="[null]" period3_date="[null]"-->
+ <!--period4_mode="[null]" period4_param="[null]" period4_date="[null]"-->
+ <!--period5_mode="[null]" period5_param="[null]" period5_date="[null]"-->
+ <!--depth="[null]" scope="DIR" qualifier="DIR" created_at="1228222680000" build_date="1228222680000" version="[null]" path="[null]"/>-->
+
+
+ <!--<snapshots id="6"-->
+ <!--project_id="3" parent_snapshot_id="5" root_project_id="1" root_snapshot_id="4"-->
+ <!--status="P" islast="[false]" purge_status="1"-->
+ <!--period1_mode="[null]" period1_param="[null]" period1_date="[null]"-->
+ <!--period2_mode="[null]" period2_param="[null]" period2_date="[null]"-->
+ <!--period3_mode="[null]" period3_param="[null]" period3_date="[null]"-->
+ <!--period4_mode="[null]" period4_param="[null]" period4_date="[null]"-->
+ <!--period5_mode="[null]" period5_param="[null]" period5_date="[null]"-->
+ <!--depth="[null]" scope="FIL" qualifier="FIL" created_at="1228222680000" build_date="1228222680000" version="[null]" path="[null]"/>-->
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles.xml
new file mode 100644
index 00000000000..b7089187ced
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteHistoricalDataOfDirectoriesAndFiles.xml
@@ -0,0 +1,94 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" enabled="[true]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="." created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the directory -->
+ <projects id="2" enabled="[true]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD" module_uuid_path="."
+ created_at="[null]"
+ long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- the file -->
+ <projects id="3" enabled="[true]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+ module_uuid_path=".ABCD." created_at="[null]"
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+ deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+ <!-- do not purge last snapshots -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="DIR" qualifier="DIR" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <snapshots id="3"
+ project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="FIL" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshots to be purged -->
+ <snapshots id="4"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <snapshots id="5"
+ project_id="2" parent_snapshot_id="4" root_project_id="1" root_snapshot_id="4"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="DIR" qualifier="DIR" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <snapshots id="6"
+ project_id="3" parent_snapshot_id="5" root_project_id="1" root_snapshot_id="4"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="FIL" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteProject.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteProject.xml
new file mode 100644
index 00000000000..1f554aded5d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteProject.xml
@@ -0,0 +1,108 @@
+<dataset>
+
+ <!-- root -->
+ <projects id="1" enabled="[true]" root_id="[null]"
+ uuid="A" project_uuid="A" module_uuid="[null]" module_uuid_path="."
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <action_plans id="1" kee="ABCD" project_id="1" name="SHORT_TERM" description="[null]" deadline="[null]"
+ user_login="igor" status="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <issues id="1" kee="ABCDE" component_uuid="A" project_uuid="A" status="CLOSED" resolution="[null]" line="200"
+ severity="BLOCKER"
+ reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]"
+ action_plan_key="[null]"
+ created_at="[null]"
+ updated_at="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ />
+
+ <issues id="2" kee="ABCDF" component_uuid="A" project_uuid="A" status="CLOSED" resolution="[null]" line="200"
+ severity="BLOCKER"
+ reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]"
+ action_plan_key="[null]"
+ created_at="[null]"
+ updated_at="[null]"
+ issue_creation_date="1366063200000"
+ issue_update_date="1366063200000"
+ issue_close_date="1366063200000"
+ />
+
+ <issue_changes id="1" kee="[null]" issue_key="ABCDF" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc"/>
+
+ <!-- modules -->
+ <projects id="2" enabled="[true]" root_id="1"
+ uuid="B" project_uuid="A" module_uuid="A" module_uuid_path=".A."
+ long_name="[null]" scope="PRJ" qualifier="BRC" kee="module1" name="module1"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <projects id="3" enabled="[false]" root_id="1"
+ uuid="C" project_uuid="A" module_uuid="A" module_uuid_path=".A."
+ long_name="[null]" scope="PRJ" qualifier="BRC" kee="module2" name="module2"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="3" project_id="3" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="BRC" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- file of module 2-->
+ <projects id="4" enabled="[false]" root_id="3"
+ uuid="D" project_uuid="A" module_uuid="C" module_uuid_path=".A.C."
+ long_name="[null]" scope="FIL" qualifier="FIL" kee="module2:File.java" name="File"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="FIL" qualifier="FIL" created_at="1228222680000"
+ build_date="1228222680000"
+ version="[null]" path="[null]"/>
+ <file_sources id="1" project_uuid="A" file_uuid="D" binary_data="[null]" line_hashes="[null]" data_hash="321654987"
+ created_at="123456789" updated_at="123456789" data_type="SOURCE"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots-result.xml
new file mode 100644
index 00000000000..18f56865b25
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots-result.xml
@@ -0,0 +1,37 @@
+<dataset>
+
+ <!-- do not delete if islast=true -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- delete only resource 1 -->
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- to be deleted -->
+ <!--<snapshots id="3"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000" version="[null]" path="[null]"/>-->
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots.xml
new file mode 100644
index 00000000000..b22f1e45c20
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldDeleteSnapshots.xml
@@ -0,0 +1,38 @@
+<dataset>
+
+ <!-- do not delete if islast=true -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- delete only resource 1 -->
+ <snapshots id="2"
+ project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- to be deleted -->
+ <snapshots id="3"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject-result.xml
new file mode 100644
index 00000000000..1072cf24714
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject-result.xml
@@ -0,0 +1,49 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="." enabled="[true]"
+ created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ root_id="[null]" description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ path="[null]" deprecated_kee="[null]"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- snapshot already purged -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <!-- do not purge snapshot with islast=true-->
+ <snapshots id="2"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshot to be purged -->
+ <snapshots id="3"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject.xml
new file mode 100644
index 00000000000..034d3856aaf
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldPurgeProject.xml
@@ -0,0 +1,49 @@
+<dataset>
+
+ <!-- the project -->
+ <projects id="1" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]" module_uuid_path="." enabled="[true]"
+ created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ root_id="[null]" description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ path="[null]" deprecated_kee="[null]"
+ authorization_updated_at="[null]"/>
+
+
+ <!-- snapshot already purged -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="1"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+
+ <!-- do not purge snapshot with islast=true-->
+ <snapshots id="2"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- snapshot to be purged -->
+ <snapshots id="3"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml
new file mode 100644
index 00000000000..d523d563dc7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/shouldSelectPurgeableSnapshots.xml
@@ -0,0 +1,68 @@
+<dataset>
+
+ <!-- last -> select -->
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- not processed -> exclude -->
+ <snapshots id="2"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="U" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- on other resource -> exclude -->
+ <snapshots id="3"
+ project_id="222" parent_snapshot_id="[null]" root_project_id="222" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- without event -> select -->
+ <snapshots id="4"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- with event -> select -->
+ <snapshots id="5"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[false]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <events id="2" component_uuid="1" snapshot_id="5"
+ category="Version" description="[null]" name="Version 1.0" event_date="1228222680000"
+ created_at="1228222680000"
+ event_data="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml
new file mode 100644
index 00000000000..142d23dcfe0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml
@@ -0,0 +1,89 @@
+<!--
+
+ EXACTLY THE SAME FILE. NO ROWS HAVE BEEN DELETED.
+
+-->
+<dataset>
+
+ <projects id="1" uuid="1" enabled="[true]" root_id="[null]" created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+
+ <!-- old closed issues on file and project -->
+ <!--
+ <issues id="1" kee="ISSUE-1"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000" created_at="2013-04-16"/>
+ <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="2" kee="ISSUE-2"
+ component_uuid="1"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000" created_at="2013-04-16"/>
+ <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+ -->
+
+ <!-- old open issues -->
+ <issues id="3" kee="ISSUE-3"
+ component_uuid="1"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]" tags="[null]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <!-- recent open and closed issues -->
+ <issues id="4" kee="ISSUE-4"
+ component_uuid="100"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]" tags="[null]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <!--
+ <issues id="5" kee="ISSUE-5"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1735686000000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000" created_at="2013-04-16"/>
+ <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+ -->
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues.xml
new file mode 100644
index 00000000000..9fe05024ebd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_all_closed_issues.xml
@@ -0,0 +1,94 @@
+<dataset>
+
+ <projects id="1" uuid="1" enabled="[true]" root_id="[null]" created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+
+ <!-- old closed issues on file and project -->
+ <issues id="1" kee="ISSUE-1"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="2" kee="ISSUE-2"
+ component_uuid="1"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+
+ <!-- old open issues -->
+ <issues id="3" kee="ISSUE-3"
+ component_uuid="1"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <!-- recent open and closed issues -->
+ <issues id="4" kee="ISSUE-4"
+ component_uuid="100"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="5" kee="ISSUE-5"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1735686000000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml
new file mode 100644
index 00000000000..7b79eaacc03
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml
@@ -0,0 +1,86 @@
+<dataset>
+
+ <projects id="1" uuid="1" enabled="[true]" root_id="[null]" created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000" build_date="1228222680000"
+ version="[null]" path="[null]"/>
+
+ <!-- old closed issues on file and project -> to be purged -->
+ <!--
+ <issues id="1" kee="ISSUE-1"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000" created_at="2013-04-16"/>
+ <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="2" kee="ISSUE-2"
+ component_uuid="1"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000" created_at="2013-04-16"/>
+ <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+ -->
+
+ <!-- old open issues -> do not purge -->
+ <issues id="3" kee="ISSUE-3"
+ component_uuid="1"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]" tags="[null]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <!-- recent open and closed issues -> do not purge -->
+ <issues id="4" kee="ISSUE-4"
+ component_uuid="100"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]" tags="[null]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="5" kee="ISSUE-5"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1735686000000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]" tags="[null]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues.xml b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues.xml
new file mode 100644
index 00000000000..95d56051843
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/purge/PurgeDaoTest/should_delete_old_closed_issues.xml
@@ -0,0 +1,93 @@
+<dataset>
+
+ <projects id="1" uuid="1" enabled="[true]" root_id="[null]" created_at="[null]"
+ long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ description="[null]" language="java" copy_resource_id="[null]" person_id="[null]"
+ authorization_updated_at="[null]"/>
+
+ <snapshots id="1"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ status="P" islast="[true]" purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ depth="[null]" scope="PRJ" qualifier="TRK" created_at="1228222680000"
+ build_date="1228222680000" version="[null]" path="[null]"/>
+
+ <!-- old closed issues on file and project -> to be purged -->
+ <issues id="1" kee="ISSUE-1"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="2" kee="ISSUE-2"
+ component_uuid="1"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1262300400000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+
+ <!-- old open issues -> do not purge -->
+ <issues id="3" kee="ISSUE-3"
+ component_uuid="1"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <!-- recent open and closed issues -> do not purge -->
+ <issues id="4" kee="ISSUE-4"
+ component_uuid="100"
+ project_uuid="1"
+ status="OPEN"
+ issue_close_date="[null]"
+ resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+ <issues id="5" kee="ISSUE-5"
+ component_uuid="100"
+ project_uuid="1"
+ status="CLOSED"
+ issue_close_date="1735686000000"
+ resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500"
+ manual_severity="[false]"
+ message="[null]" action_plan_key="[null]" effort_to_fix="[null]" technical_debt="[null]"
+ issue_attributes="[null]" checksum="[null]" author_login="[null]"
+ updated_at="[null]" issue_creation_date="1366063200000" issue_update_date="1366063200000"
+ created_at="1400000000000"/>
+ <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin"
+ change_type="comment" change_data="abc" issue_change_creation_date="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest/shared.xml
new file mode 100644
index 00000000000..9fb6b533775
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest/shared.xml
@@ -0,0 +1,46 @@
+<dataset>
+
+ <quality_gates id="42" name="Golden"/>
+ <quality_gates id="43" name="Ninth"/>
+
+ <projects id="1" uuid="A" name="Project One" qualifier="TRK" scope="PRJ"/>
+ <projects id="2" uuid="B" name="Project Two" qualifier="TRK" scope="PRJ"/>
+ <projects id="3" uuid="C" name="Project Three" qualifier="TRK" scope="PRJ"/>
+ <projects id="4" uuid="D" name="Project Four" qualifier="TRK" scope="PRJ"/>
+ <projects id="5" uuid="E" name="Project Five" qualifier="TRK" scope="PRJ"/>
+ <projects id="6" uuid="F" name="View Six" qualifier="VW" scope="PRJ"/>
+ <projects id="7" uuid="G" name="Project One" qualifier="TRK" scope="FIL"/>
+
+ <resource_index id="1" kee="project one" resource_id="1" root_project_id="1" position="0" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="2" kee="roject one" resource_id="1" root_project_id="1" position="1" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="3" kee="oject one" resource_id="1" root_project_id="1" position="2" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="4" kee="ject one" resource_id="1" root_project_id="1" position="3" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="5" kee="ect one" resource_id="1" root_project_id="1" position="4" name_size="11" qualifier="TRK"/>
+ <resource_index id="6" kee="ct one" resource_id="1" root_project_id="1" position="5" name_size="11" qualifier="TRK"/>
+ <resource_index id="7" kee="t one" resource_id="1" root_project_id="1" position="6" name_size="11" qualifier="TRK"/>
+ <resource_index id="8" kee=" one" resource_id="1" root_project_id="1" position="7" name_size="11" qualifier="TRK"/>
+ <resource_index id="9" kee="one" resource_id="1" root_project_id="1" position="8" name_size="11" qualifier="TRK"/>
+ <resource_index id="10" kee="project two" resource_id="2" root_project_id="2" position="0" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="11" kee="roject two" resource_id="2" root_project_id="2" position="1" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="12" kee="oject two" resource_id="2" root_project_id="2" position="2" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="13" kee="ject two" resource_id="2" root_project_id="2" position="3" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="14" kee="ect two" resource_id="2" root_project_id="2" position="4" name_size="11"
+ qualifier="TRK"/>
+ <resource_index id="15" kee="ct two" resource_id="2" root_project_id="2" position="5" name_size="11" qualifier="TRK"/>
+ <resource_index id="16" kee="t two" resource_id="2" root_project_id="2" position="6" name_size="11" qualifier="TRK"/>
+ <resource_index id="17" kee=" two" resource_id="2" root_project_id="2" position="7" name_size="11" qualifier="TRK"/>
+
+ <properties id="1" prop_key="sonar.qualitygate" resource_id="[null]" text_value="43"/>
+ <properties id="2" prop_key="sonar.qualitygate" resource_id="1" text_value="42"/>
+ <properties id="3" prop_key="sonar.qualitygate" resource_id="2" text_value="42"/>
+ <properties id="4" prop_key="sonar.qualitygate" resource_id="3" text_value="42"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/delete-result.xml
new file mode 100644
index 00000000000..62f82411cd7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/delete-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <quality_gate_conditions id="2" qgate_id="1" metric_id="3" operator="&lt;" value_warning="10" value_error="20"
+ period="[null]"/>
+ <quality_gate_conditions id="3" qgate_id="1" metric_id="4" operator="&lt;" value_warning="10" value_error="[null]"
+ period="1"/>
+ <quality_gate_conditions id="4" qgate_id="2" metric_id="5" operator="&lt;" value_warning="[null]" value_error="20"
+ period="3"/>
+ <quality_gate_conditions id="5" qgate_id="2" metric_id="6" operator="&lt;" value_warning="[null]" value_error="20"
+ period="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert-result.xml
new file mode 100644
index 00000000000..20aa1a86ee7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <quality_gate_conditions id="1" qgate_id="1" metric_id="2" operator="GT" value_warning="10" value_error="20"
+ period="3"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/insert.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/selectForQualityGate.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/selectForQualityGate.xml
new file mode 100644
index 00000000000..82faf6d5f15
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/selectForQualityGate.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <quality_gate_conditions id="1" qgate_id="1" metric_id="2" operator="&lt;" value_warning="10" value_error="20"
+ period="3"/>
+ <quality_gate_conditions id="2" qgate_id="1" metric_id="3" operator="&lt;" value_warning="10" value_error="20"
+ period="[null]"/>
+ <quality_gate_conditions id="3" qgate_id="1" metric_id="4" operator="&lt;" value_warning="10" value_error="[null]"
+ period="1"/>
+ <quality_gate_conditions id="4" qgate_id="2" metric_id="5" operator="&lt;" value_warning="[null]" value_error="20"
+ period="3"/>
+ <quality_gate_conditions id="5" qgate_id="2" metric_id="6" operator="&lt;" value_warning="[null]" value_error="20"
+ period="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions-result.xml
new file mode 100644
index 00000000000..7179362495c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions-result.xml
@@ -0,0 +1,24 @@
+<dataset>
+
+ <metrics delete_historical_data="false" id="1" name="key1" val_type="INT" description="description" domain="domain"
+ short_name="name1" qualitative="false" user_managed="false" enabled="true" worst_value="[null]"
+ optimized_best_value="false" best_value="[null]" direction="1" hidden="false"/>
+
+ <metrics delete_historical_data="false" id="2" name="disabledkey2" val_type="INT" description="description"
+ domain="domain"
+ short_name="name2" qualitative="false" user_managed="false" enabled="false" worst_value="[null]"
+ optimized_best_value="false" best_value="[null]" direction="1" hidden="false"/>
+
+ <!-- ok -->
+ <quality_gate_conditions id="1" qgate_id="1" metric_id="1" operator="GT" value_error="30" value_warning="[null]"
+ period="[null]"/>
+ <quality_gate_conditions id="2" qgate_id="2" metric_id="1" operator="GT" value_error="[null]" value_warning="150"
+ period="[null]"/>
+
+ <!-- disabled metric -->
+ <!--<quality_gate_conditions id="3" qgate_id="1" metric_id="2" operator="GT" value_error="30" value_warning="[null]" period="[null]"/>-->
+
+ <!-- unknown metric -->
+ <!--<quality_gate_conditions id="4" qgate_id="1" metric_id="999" operator="GT" value_error="30" value_warning="[null]" period="[null]"/>-->
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions.xml
new file mode 100644
index 00000000000..402d75a704c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/shouldCleanConditions.xml
@@ -0,0 +1,26 @@
+<dataset>
+
+ <metrics delete_historical_data="false" id="1" name="key1" val_type="INT" description="description" domain="domain"
+ short_name="name1" qualitative="false" user_managed="false" enabled="true" worst_value="[null]"
+ optimized_best_value="false" best_value="[null]" direction="1" hidden="false"/>
+
+ <metrics delete_historical_data="false" id="2" name="disabledkey2" val_type="INT" description="description"
+ domain="domain"
+ short_name="name2" qualitative="false" user_managed="false" enabled="false" worst_value="[null]"
+ optimized_best_value="false" best_value="[null]" direction="1" hidden="false"/>
+
+ <!-- ok -->
+ <quality_gate_conditions id="1" qgate_id="1" metric_id="1" operator="GT" value_error="30" value_warning="[null]"
+ period="[null]"/>
+ <quality_gate_conditions id="2" qgate_id="2" metric_id="1" operator="GT" value_error="[null]" value_warning="150"
+ period="[null]"/>
+
+ <!-- disabled metric -->
+ <quality_gate_conditions id="3" qgate_id="1" metric_id="2" operator="GT" value_error="30" value_warning="[null]"
+ period="[null]"/>
+
+ <!-- unknown metric -->
+ <quality_gate_conditions id="4" qgate_id="1" metric_id="999" operator="GT" value_error="30" value_warning="[null]"
+ period="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/update-result.xml
new file mode 100644
index 00000000000..1364e7c51e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateConditionDaoTest/update-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <quality_gate_conditions id="1" qgate_id="1" metric_id="7" operator="&gt;" value_warning="50" value_error="80"
+ period="1"/>
+ <quality_gate_conditions id="2" qgate_id="1" metric_id="3" operator="&lt;" value_warning="10" value_error="20"
+ period="[null]"/>
+ <quality_gate_conditions id="3" qgate_id="1" metric_id="4" operator="&lt;" value_warning="10" value_error="[null]"
+ period="1"/>
+ <quality_gate_conditions id="4" qgate_id="2" metric_id="5" operator="&lt;" value_warning="[null]" value_error="20"
+ period="3"/>
+ <quality_gate_conditions id="5" qgate_id="2" metric_id="6" operator="&lt;" value_warning="[null]" value_error="20"
+ period="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/delete-result.xml
new file mode 100644
index 00000000000..3c0ddc793c1
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/delete-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <quality_gates id="2" name="Balanced"/>
+ <quality_gates id="3" name="Lenient"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert-result.xml
new file mode 100644
index 00000000000..00881f2945b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <quality_gates id="1" name="My Quality Gate"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/insert.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/selectAll.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/selectAll.xml
new file mode 100644
index 00000000000..6eb3e577e4e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/selectAll.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <quality_gates id="1" name="Very strict"/>
+ <quality_gates id="2" name="Balanced"/>
+ <quality_gates id="3" name="Lenient"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/update-result.xml
new file mode 100644
index 00000000000..4f80f313eae
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualitygate/QualityGateDaoTest/update-result.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <quality_gates id="1" name="Not so strict"/>
+ <quality_gates id="2" name="Balanced"/>
+ <quality_gates id="3" name="Lenient"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete-result.xml
new file mode 100644
index 00000000000..2283bba7d52
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete-result.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <!--<active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2" inheritance="INHERITED"-->
+
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1"
+ inheritance="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_profile-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_profile-result.xml
new file mode 100644
index 00000000000..617cd7e08b0
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_profile-result.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <!--<active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0" inheritance="[null]"-->
+
+ <!--<active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1" inheritance="[null]"-->
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_rule-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_rule-result.xml
new file mode 100644
index 00000000000..a06288e57b6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_from_rule-result.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <!--<active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1" inheritance="[null]"/>-->
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameter-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameter-result.xml
new file mode 100644
index 00000000000..900eb2c2874
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameter-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <!--<active_rule_parameters id="1" active_rule_id="1" rules_parameter_id="1" rules_parameter_key="max" value="20"/>-->
+ <active_rule_parameters id="2" active_rule_id="1" rules_parameter_id="2" rules_parameter_key="format" value="html"/>
+
+ <active_rule_parameters id="3" active_rule_id="2" rules_parameter_id="1" rules_parameter_key="max" value="15"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters-result.xml
new file mode 100644
index 00000000000..b8aba08833d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <!--<active_rule_parameters id="1" active_rule_id="1" rules_parameter_id="1" rules_parameter_key="max" value="20"/>-->
+ <!--<active_rule_parameters id="2" active_rule_id="1" rules_parameter_id="2" rules_parameter_key="format" value="html"/>-->
+
+ <active_rule_parameters id="3" active_rule_id="2" rules_parameter_id="1" rules_parameter_key="max" value="15"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id-result.xml
new file mode 100644
index 00000000000..d1bbff5912e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id-result.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <!-- Parent of Active rule 1 -->
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1"
+ inheritance="[null]"/>
+
+ <active_rule_parameters id="1" active_rule_id="1" rules_parameter_id="1" rules_parameter_key="max" value="20"/>
+ <active_rule_parameters id="2" active_rule_id="1" rules_parameter_id="2" rules_parameter_key="format" value="html"/>
+
+ <!--<active_rule_parameters id="3" active_rule_id="2" rules_parameter_id="1" rules_parameter_key="max" value="15"/>-->
+ <!--<active_rule_parameters id="4" active_rule_id="3" rules_parameter_id="2" rules_parameter_key="format" value="text"/>-->
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id.xml
new file mode 100644
index 00000000000..47c6ceeac24
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/delete_parameters_from_profile_id.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <!-- Parent of Active rule 1 -->
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1"
+ inheritance="[null]"/>
+
+ <active_rule_parameters id="1" active_rule_id="1" rules_parameter_id="1" rules_parameter_key="max" value="20"/>
+ <active_rule_parameters id="2" active_rule_id="1" rules_parameter_id="2" rules_parameter_key="format" value="html"/>
+
+ <active_rule_parameters id="3" active_rule_id="2" rules_parameter_id="1" rules_parameter_key="max" value="15"/>
+ <active_rule_parameters id="4" active_rule_id="3" rules_parameter_id="2" rules_parameter_key="format" value="text"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/empty.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/empty.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/empty.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert-result.xml
new file mode 100644
index 00000000000..574cbabdd56
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert_parameter-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert_parameter-result.xml
new file mode 100644
index 00000000000..ed2e17d48d3
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/insert_parameter-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <active_rule_parameters id="1" active_rule_id="1" rules_parameter_id="1" rules_parameter_key="max" value="20"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/shared.xml
new file mode 100644
index 00000000000..b6d5461c10d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/shared.xml
@@ -0,0 +1,22 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="2"
+ inheritance="INHERITED"/>
+
+ <!-- Parent of Active rule 1 -->
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1"
+ inheritance="[null]"/>
+
+ <active_rule_parameters id="1" active_rule_id="1" rules_parameter_id="1" rules_parameter_key="max" value="20"/>
+ <active_rule_parameters id="2" active_rule_id="1" rules_parameter_id="2" rules_parameter_key="format" value="html"/>
+
+ <active_rule_parameters id="3" active_rule_id="2" rules_parameter_id="1" rules_parameter_key="max" value="15"/>
+
+ <rules_profiles id="1" name="Child" language="java" parent_kee="parent" kee="child" is_default="[false]"/>
+
+ <rules_profiles id="2" name="Parent" language="java" parent_kee="[null]" kee="parent" is_default="[false]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update-result.xml
new file mode 100644
index 00000000000..30111d636a7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update-result.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="1" profile_id="1" rule_id="10" failure_level="4"
+ inheritance="[null]"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="2" profile_id="2" rule_id="10" failure_level="0"
+ inheritance="[null]"/>
+
+ <active_rules created_at="[null]" updated_at="[null]" id="3" profile_id="2" rule_id="11" failure_level="1"
+ inheritance="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update_parameter-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update_parameter-result.xml
new file mode 100644
index 00000000000..289da2f39f2
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/ActiveRuleDaoTest/update_parameter-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <active_rule_parameters id="1" active_rule_id="2" rules_parameter_id="3" rules_parameter_key="newMax" value="30"/>
+ <active_rule_parameters id="2" active_rule_id="1" rules_parameter_id="2" rules_parameter_key="format" value="html"/>
+
+ <active_rule_parameters id="3" active_rule_id="2" rules_parameter_id="1" rules_parameter_key="max" value="15"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/delete-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/delete-result.xml
new file mode 100644
index 00000000000..fac2543235e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/delete-result.xml
@@ -0,0 +1,6 @@
+<dataset>
+
+ <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/inheritance.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/inheritance.xml
new file mode 100644
index 00000000000..4768bc64707
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/inheritance.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <rules_profiles id="1" name="Child1" language="java" parent_kee="java_parent" kee="java_child1" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="2" name="Child2" language="java" parent_kee="java_parent" kee="java_child2" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="3" name="Parent" language="java" parent_kee="[null]" kee="java_parent" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <!-- Same profile for another language -->
+
+ <rules_profiles id="4" name="Child1" language="js" parent_kee="js_parent" kee="js_child1" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="5" name="Child2" language="js" parent_kee="js_parent" kee="js_child2" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="6" name="Parent" language="js" parent_kee="[null]" kee="js_parent" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/insert-result.xml
new file mode 100644
index 00000000000..8a9e9ce1ada
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/insert-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <rules_profiles id="1" name="Sonar Way" language="java" parent_kee="[null]" kee="java_sonar_way" is_default="[true]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="3" name="ABCDE" language="xoo" parent_kee="[null]" kee="abcde" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/projects.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/projects.xml
new file mode 100644
index 00000000000..94df8ad98ee
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/projects.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <rules_profiles id="1" name="Sonar Way" language="java" parent_kee="[null]" kee="java_sonar_way" is_default="[true]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+ <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[true]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <projects id="1" uuid="A" kee="org.codehaus.sonar:sonar" name="SonarQube" enabled="[true]"/>
+ <projects id="2" uuid="B" kee="org.codehaus.sonar-plugins.java:java" name="SonarQube Java" enabled="[true]"/>
+ <projects id="3" uuid="C" kee="disabled:project" name="Disabled Project" enabled="[false]"/>
+
+ <project_qprofiles id="1" project_uuid="A" profile_key="java_sonar_way"/>
+ <project_qprofiles id="2" project_uuid="B" profile_key="java_sonar_way"/>
+ <project_qprofiles id="3" project_uuid="A" profile_key="js_sonar_way"/>
+ <project_qprofiles id="4" project_uuid="B" profile_key="js_sonar_way"/>
+ <project_qprofiles id="5" project_uuid="C" profile_key="js_sonar_way"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_all_is_sorted_by_profile_name.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_all_is_sorted_by_profile_name.xml
new file mode 100644
index 00000000000..94e35a7f936
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_all_is_sorted_by_profile_name.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <rules_profiles id="3" name="Third" language="js" parent_kee="[null]" kee="js_third" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="1" name="First" language="js" parent_kee="[null]" kee="js_first" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="2" name="Second" language="js" parent_kee="[null]" kee="js_second" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_by_language.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_by_language.xml
new file mode 100644
index 00000000000..05dbd930efd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/select_by_language.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <rules_profiles id="1" name="Sonar Way 1" language="java" parent_kee="[null]" kee="java_sonar_way"
+ is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="3" name="Sonar Way 2" language="java" parent_kee="[null]" kee="java_sonar_way2"
+ is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/shared.xml
new file mode 100644
index 00000000000..bf8e0113b24
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/shared.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <rules_profiles id="1" name="Sonar Way" language="java" parent_kee="[null]" kee="java_sonar_way" is_default="[true]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/update-result.xml
new file mode 100644
index 00000000000..3d54167f58d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/update-result.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <rules_profiles id="1" name="New Name" language="js" parent_kee="fghij" kee="java_sonar_way" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+ <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
+ rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/empty.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/empty.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/empty.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert-result.xml
new file mode 100644
index 00000000000..708daeb2aa8
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert-result.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="NewRuleKey" plugin_name="plugin" name="new name"
+ description="new description" status="DEPRECATED"
+ plugin_config_key="NewConfigKey" priority="0" is_template="[true]" language="dart" created_at="2013-12-16"
+ updated_at="2013-12-17" template_id="3"
+ note_data="[null]" note_user_login="[null]" note_created_at="[null]" note_updated_at="[null]"
+ characteristic_id="100" default_characteristic_id="101"
+ remediation_function="linear" default_remediation_function="linear_offset"
+ remediation_coeff="1h" default_remediation_coeff="5d"
+ remediation_offset="5min" default_remediation_offset="10h"
+ effort_to_fix_description="squid.S115.effortToFix"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_all-result.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_all-result.xml
new file mode 100644
index 00000000000..71c46fb15b7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_all-result.xml
@@ -0,0 +1,47 @@
+<!--
+ ~ SonarQube, open source software quality management tool.
+ ~ Copyright (C) 2008-2014 SonarSource
+ ~ mailto:contact AT sonarsource DOT com
+ ~
+ ~ SonarQube 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.
+ ~
+ ~ SonarQube 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 program; if not, write to the Free Software Foundation,
+ ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ -->
+
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="NewRuleKey" plugin_name="plugin" name="new name"
+ description="new description" status="DEPRECATED"
+ plugin_config_key="NewConfigKey" priority="0" is_template="[true]" language="dart" created_at="2013-12-16"
+ updated_at="2013-12-17" template_id="3"
+ note_data="[null]" note_user_login="[null]" note_created_at="[null]" note_updated_at="[null]"
+ characteristic_id="100" default_characteristic_id="101"
+ remediation_function="linear" default_remediation_function="linear_offset"
+ remediation_coeff="1h" default_remediation_coeff="5d"
+ remediation_offset="5min" default_remediation_offset="10h"
+ effort_to_fix_description="squid.S115.effortToFix"
+ />
+
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="NewRuleKey2" plugin_name="plugin2" name="new name2"
+ description="new description2" status="BETA"
+ plugin_config_key="NewConfigKey2" priority="2" is_template="[false]" language="js" created_at="2013-12-14"
+ updated_at="2013-12-15" template_id="[null]"
+ note_data="[null]" note_user_login="[null]" note_created_at="[null]" note_updated_at="[null]"
+ characteristic_id="102" default_characteristic_id="103"
+ remediation_function="linear_offset" default_remediation_function="linear"
+ remediation_coeff="5d" default_remediation_coeff="1h"
+ remediation_offset="10h" default_remediation_offset="5min"
+ effort_to_fix_description="squid.S115.effortToFix2"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter-result.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter-result.xml
new file mode 100644
index 00000000000..5208b7a4a4c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter-result.xml
@@ -0,0 +1,3 @@
+<dataset>
+ <rules_parameters id="1" rule_id="1" name="max" param_type="INTEGER" default_value="30" description="My Parameter"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter.xml
new file mode 100644
index 00000000000..871dedcb5e9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/insert_parameter.xml
@@ -0,0 +1,3 @@
+<dataset>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectAll.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectAll.xml
new file mode 100644
index 00000000000..d957463ec95
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectAll.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"
+ note_data="Rule note with accents éèà" note_user_login="polop.palap" note_created_at="2013-12-25"
+ characteristic_id="100" default_characteristic_id="101"
+ remediation_function="linear" default_remediation_function="linear_offset"
+ remediation_coeff="1h" default_remediation_coeff="5d"
+ remediation_offset="5min" default_remediation_offset="10h"
+ effort_to_fix_description="squid.S115.effortToFix"
+ />
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectById.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectById.xml
new file mode 100644
index 00000000000..b4a2a04621d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectById.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidComparison" plugin_name="checkstyle"
+ name="Avoid Comparison" description="Should avoid ==" status="READY"/>
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectNonManual.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectNonManual.xml
new file mode 100644
index 00000000000..22f7c745985
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectNonManual.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"/>
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="AvoidNull" plugin_name="manual" name="Manual Rule"
+ description="Should not appear" status="READY"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectParameters.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectParameters.xml
new file mode 100644
index 00000000000..8f5e8a92b95
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/selectParameters.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"/>
+ <rules_parameters id="1" rule_id="1" name="myParameter" param_type="plop" default_value="plouf"
+ description="My Parameter"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_name.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_name.xml
new file mode 100644
index 00000000000..b4a2a04621d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_name.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidComparison" plugin_name="checkstyle"
+ name="Avoid Comparison" description="Should avoid ==" status="READY"/>
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_rule_key.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_rule_key.xml
new file mode 100644
index 00000000000..b4a2a04621d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_rule_key.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidComparison" plugin_name="checkstyle"
+ name="Avoid Comparison" description="Should avoid ==" status="READY"/>
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_sub_characteristic_id.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_sub_characteristic_id.xml
new file mode 100644
index 00000000000..1dbd55a539f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_by_sub_characteristic_id.xml
@@ -0,0 +1,74 @@
+<dataset>
+
+ <!-- Root characteristic -->
+ <characteristics id="1" kee="PORTABILITY" name="Portability" parent_id="[null]" characteristic_order="1"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Sub characteristics of root characteristic -->
+ <characteristics id="2" kee="COMPILER_RELATED_PORTABILITY" name="Compiler related portability" parent_id="1"
+ characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+ <characteristics id="3" kee="HARDWARE_RELATED_PORTABILITY" name="Hardware related portability " parent_id="1"
+ characteristic_order="[null]"
+ enabled="[true]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled root characteristic -->
+ <characteristics id="10" kee="DISABLED_ROOT_CHARACTERISTIC" name="Disabled root characteristic" parent_id="[null]"
+ characteristic_order="2"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Disabled characteristic -->
+ <characteristics id="11" kee="DISABLED_CHARACTERISTIC" name="Disabled characteristic" parent_id="10"
+ characteristic_order="[null]"
+ enabled="[false]"
+ created_at="2013-11-20" updated_at="2013-11-22"/>
+
+ <!-- Rule linked to a sub characteristic -->
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="UselessImportCheck" plugin_name="squid"
+ name="UselessImportCheck" description="Useless imports should be removed" status="READY"
+ characteristic_id="2" default_characteristic_id="50"
+ remediation_function="LINEAR_OFFSET" default_remediation_function="LINEAR_OFFSET"
+ remediation_coeff="5d" default_remediation_coeff="5d"
+ remediation_offset="10h" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+ <!-- Rule linked to a sub characteristic -->
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="LeftCurlyBraceStartLineCheck" plugin_name="squid"
+ name="LeftCurlyBraceStartLineCheck"
+ description="Left curly braces should be located at the beginning of lines of code" status="READY"
+ characteristic_id="3" default_characteristic_id="50"
+ remediation_function="LINEAR_OFFSET" default_remediation_function="LINEAR_OFFSET"
+ remediation_coeff="5d" default_remediation_coeff="5d"
+ remediation_offset="10h" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+ <!-- Rule linked to a disabled sub characteristic -> should never be returned -->
+ <rules tags="[null]" system_tags="[null]" id="3" plugin_rule_key="CallToFileDeleteOnExitMethod" plugin_name="squid"
+ name="CallToFileDeleteOnExitMethod" description="CallToFileDeleteOnExitMethod" status="READY"
+ characteristic_id="11" default_characteristic_id="50"
+ remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+ remediation_coeff="5d" default_remediation_coeff="5d"
+ remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+ <!-- Removed rule linked to one enable sub characteristic -->
+ <rules tags="[null]" system_tags="[null]" id="4" plugin_rule_key="ObjectFinalizeOverridenCallsSuperFinalizeCheck"
+ plugin_name="squid" name="ObjectFinalizeOverridenCallsSuperFinalizeCheck"
+ description="super.finalize() should be called at the end of Object.finalize() implementations"
+ status="REMOVED"
+ characteristic_id="3" default_characteristic_id="50"
+ remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+ remediation_coeff="5d" default_remediation_coeff="5min"
+ remediation_offset="[null]" default_remediation_offset="10h" updated_at="2014-02-19"/>
+
+ <!-- Rule linked to a sub characteristic, but only default characteristic is linked -->
+ <rules tags="[null]" system_tags="[null]" id="5" plugin_rule_key="RightCurlyBraceStartLineCheck" plugin_name="squid"
+ name="RightCurlyBraceStartLineCheck"
+ description="Right curly braces should be located at the beginning of lines of code" status="READY"
+ characteristic_id="[null]" default_characteristic_id="3"
+ remediation_function="[null]" default_remediation_function="LINEAR"
+ remediation_coeff="[null]" default_remediation_coeff="5d"
+ remediation_offset="[null]" default_remediation_offset="[null]" updated_at="2014-02-19"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_enables_and_non_manual.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_enables_and_non_manual.xml
new file mode 100644
index 00000000000..9d7df0a738b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_enables_and_non_manual.xml
@@ -0,0 +1,31 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"
+ note_data="Rule note with accents éèà" note_user_login="polop.palap" note_created_at="2013-12-25"
+ description_format="HTML"
+ characteristic_id="100" default_characteristic_id="101"
+ remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+ remediation_coeff="1h" default_remediation_coeff="5d"
+ remediation_offset="5min" default_remediation_offset="10h"
+ effort_to_fix_description="squid.S115.effortToFix"/>
+
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="AvoidNull" plugin_name="squid" name="Avoid Null"
+ description="Should avoid NULL" status="REMOVED"
+ note_data="[null]" note_user_login="[null]" note_created_at="[null]" description_format="HTML"
+ characteristic_id="[null]" default_characteristic_id="[null]"
+ remediation_function="[null]" default_remediation_function="[null]"
+ remediation_coeff="[null]" default_remediation_coeff="[null]"
+ remediation_offset="[null]" default_remediation_offset="[null]"
+ effort_to_fix_description="[null]"/>
+
+ <rules tags="[null]" system_tags="[null]" id="3" plugin_rule_key="AvoidNull" plugin_name="manual" name="Manual Rule"
+ description="Should not appear" status="READY"
+ note_data="[null]" note_user_login="[null]" note_created_at="[null]" description_format="HTML"
+ characteristic_id="[null]" default_characteristic_id="[null]"
+ remediation_function="[null]" default_remediation_function="[null]"
+ remediation_coeff="[null]" default_remediation_coeff="[null]"
+ remediation_offset="[null]" default_remediation_offset="[null]"
+ effort_to_fix_description="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_id.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_id.xml
new file mode 100644
index 00000000000..c137ab0e4cf
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_id.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"/>
+ <rules_parameters id="1" rule_id="1" name="myParameter" param_type="plop" default_value="plouf"
+ description="My Parameter"/>
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="Unused" plugin_name="unused" name="Unused Rule"
+ description="Not used" status="REMOVED"/>
+ <rules_parameters id="2" rule_id="2" name="otherParam" param_type="plop" default_value="plouf"
+ description="Other Parameter"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_ids.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_ids.xml
new file mode 100644
index 00000000000..f2c208ef87b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/select_parameters_by_rule_ids.xml
@@ -0,0 +1,9 @@
+<dataset>
+
+ <rules_parameters id="1" rule_id="1" name="myParameter" param_type="plop" default_value="plouf"
+ description="My Parameter"/>
+
+ <rules_parameters id="2" rule_id="2" name="otherParam" param_type="plop" default_value="plouf"
+ description="Other Parameter"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update-result.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update-result.xml
new file mode 100644
index 00000000000..13a9ef4b6eb
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update-result.xml
@@ -0,0 +1,27 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="NewRuleKey" plugin_name="plugin" name="new name"
+ description="new description" status="DEPRECATED"
+ plugin_config_key="NewConfigKey" priority="0" is_template="[true]" language="dart"
+ created_at="2011-04-25 01:05:00" updated_at="2013-12-17" template_id="3"
+ note_data="My note" note_user_login="admin" note_created_at="2013-12-19" note_updated_at="2013-12-20"
+ characteristic_id="100" default_characteristic_id="101"
+ remediation_function="linear" default_remediation_function="linear_offset"
+ remediation_coeff="1h" default_remediation_coeff="5d"
+ remediation_offset="5min" default_remediation_offset="10h"
+ effort_to_fix_description="squid.S115.effortToFix"
+ />
+
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="Parent1" plugin_name="checkstyle"
+ name="Parent Rule 1" description="Parent Rule 1" status="READY"
+ plugin_config_key="Parent1" priority="2" is_template="[false]" language="golo" created_at="1981-10-24 15:20:00"
+ updated_at="[null]" template_id="[null]"
+ note_created_at="[null]" note_data="[null]" note_updated_at="[null]" note_user_login="[null]"/>
+
+ <rules tags="[null]" system_tags="[null]" id="3" plugin_rule_key="Parent2" plugin_name="checkstyle"
+ name="Parent Rule 2" description="Parent Rule 2" status="READY"
+ plugin_config_key="Parent2" priority="2" is_template="[false]" language="dart" created_at="1982-12-14 03:15:00"
+ updated_at="[null]" template_id="[null]"
+ note_created_at="[null]" note_data="[null]" note_updated_at="[null]" note_user_login="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update.xml
new file mode 100644
index 00000000000..5813974016a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle"
+ name="Avoid Null" description="Should avoid NULL" status="READY"
+ plugin_config_key="AvoidNull" priority="2" is_template="[false]" language="golo"
+ created_at="2011-04-25 01:05:00" template_id="2"/>
+
+ <rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="Parent1" plugin_name="checkstyle"
+ name="Parent Rule 1" description="Parent Rule 1" status="READY"
+ plugin_config_key="Parent1" priority="2" is_template="[false]" language="golo"
+ created_at="1981-10-24 15:20:00"/>
+
+ <rules tags="[null]" system_tags="[null]" id="3" plugin_rule_key="Parent2" plugin_name="checkstyle"
+ name="Parent Rule 2" description="Parent Rule 2" status="READY"
+ plugin_config_key="Parent2" priority="2" is_template="[false]" language="dart"
+ created_at="1982-12-14 03:15:00"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter-result.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter-result.xml
new file mode 100644
index 00000000000..25188f3a97e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter-result.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <rules_parameters id="1" rule_id="1" name="format" param_type="STRING" default_value="^[a-z]+(\.[a-z][a-z0-9]*)*$"
+ description="Regular expression used to check the package names against."/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter.xml b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter.xml
new file mode 100644
index 00000000000..5208b7a4a4c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/rule/RuleDaoTest/update_parameter.xml
@@ -0,0 +1,3 @@
+<dataset>
+ <rules_parameters id="1" rule_id="1" name="max" param_type="INTEGER" default_value="30" description="My Parameter"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/semaphore/SemaphoreDaoTest/old_semaphore.xml b/sonar-db/src/test/resources/org/sonar/db/semaphore/SemaphoreDaoTest/old_semaphore.xml
new file mode 100644
index 00000000000..ac6cf63ee0e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/semaphore/SemaphoreDaoTest/old_semaphore.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <semaphores id="1" name="foo" checksum="acbd18db4cc2f85cedef654fccc4a4d8" created_at="1264374000000"
+ updated_at="1264374000000" locked_at="1264374000000"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path-result.xml
new file mode 100644
index 00000000000..912c502b3f9
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path-result.xml
@@ -0,0 +1,9 @@
+<dataset>
+ <projects id="1" name="developer@company.net" qualifier="DEV" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path=".ABCD."/>
+ <authors id="1" person_id="1" login="developer@company.net"/>
+
+ <projects id="2" name="developer2@company.net" qualifier="DEV" uuid="BCDE" project_uuid="BCDE" module_uuid="[null]"
+ module_uuid_path=".BCDE."/>
+ <authors id="2" person_id="2" login="developer2@company.net"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path.xml
new file mode 100644
index 00000000000..fb0854fccbe
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/add_missing_module_uuid_path.xml
@@ -0,0 +1,2 @@
+<dataset>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/countDeveloperLogins.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/countDeveloperLogins.xml
new file mode 100644
index 00000000000..42776255866
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/countDeveloperLogins.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <authors id="1" person_id="1" login="godin"/>
+ <authors id="2" person_id="1" login="evgeny"/>
+ <authors id="3" person_id="2" login="simon"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor-result.xml
new file mode 100644
index 00000000000..40e855c7588
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <authors
+ id="1"
+ person_id="13"
+ login="godin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor.xml
new file mode 100644
index 00000000000..fb0854fccbe
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthor.xml
@@ -0,0 +1,2 @@
+<dataset>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper-result.xml
new file mode 100644
index 00000000000..c7c3436d390
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <projects id="1" name="developer@company.net" qualifier="DEV" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+ module_uuid_path="."/>
+ <authors id="1" person_id="1" login="developer@company.net"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper.xml
new file mode 100644
index 00000000000..fb0854fccbe
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldInsertAuthorAndDeveloper.xml
@@ -0,0 +1,2 @@
+<dataset>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication-result.xml
new file mode 100644
index 00000000000..ddca7b45c66
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication-result.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <projects id="1" name="developer@company.net" qualifier="DEV" uuid="[null]" project_uuid="[null]" module_uuid="[null]"
+ module_uuid_path="."/>
+
+ <authors id="1" person_id="1" login="developer@company.net"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication.xml
new file mode 100644
index 00000000000..ddca7b45c66
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsAndDevelopersDuplication.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <projects id="1" name="developer@company.net" qualifier="DEV" uuid="[null]" project_uuid="[null]" module_uuid="[null]"
+ module_uuid_path="."/>
+
+ <authors id="1" person_id="1" login="developer@company.net"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication-result.xml
new file mode 100644
index 00000000000..8203f8edbc2
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication-result.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <authors id="1" person_id="10" login="godin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication.xml
new file mode 100644
index 00000000000..8203f8edbc2
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldPreventAuthorsDuplication.xml
@@ -0,0 +1,5 @@
+<dataset>
+
+ <authors id="1" person_id="10" login="godin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldSelectByLogin.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldSelectByLogin.xml
new file mode 100644
index 00000000000..40e855c7588
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorDaoTest/shouldSelectByLogin.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <authors
+ id="1"
+ person_id="13"
+ login="godin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/anonymous_should_be_authorized.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/anonymous_should_be_authorized.xml
new file mode 100644
index 00000000000..f5730087a21
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/anonymous_should_be_authorized.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <user_roles id="1" user_id="100" resource_id="999" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="[null]" resource_id="300" role="user"/>
+ <group_roles id="2" group_id="[null]" resource_id="400" role="user"/>
+
+ <projects id="301" kee="pj-w-snapshot:package" root_id="300" uuid="ABCD" module_uuid="EDFG"/>
+ <projects id="302" kee="pj-w-snapshot:file" root_id="300" uuid="BCDE" module_uuid="EDFG"/>
+ <projects id="303" kee="pj-w-snapshot:other" root_id="300" uuid="CDEF" module_uuid="EDFG"/>
+ <projects id="300" kee="pj-w-snapshot" uuid="EDFG" module_uuid="[null]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="FGHI" project_uuid="FGHI"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/group_should_be_authorized.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/group_should_be_authorized.xml
new file mode 100644
index 00000000000..7ffca0d6f5e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/group_should_be_authorized.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <!-- user 100 has no direct grant access, but is in the group 200 that has the role "user"
+ on the project 300 -->
+ <user_roles id="1" user_id="100" resource_id="999" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="200" resource_id="300" role="user"/>
+ <group_roles id="2" group_id="200" resource_id="400" role="user"/>
+
+ <projects id="301" kee="pj-w-snapshot:package" root_id="300" uuid="ABCD" module_uuid="DEFG"/>
+ <projects id="302" kee="pj-w-snapshot:file" root_id="300" uuid="BCDE" module_uuid="DEFG"/>
+ <projects id="303" kee="pj-w-snapshot:other" root_id="300" uuid="CDEF" module_uuid="DEFG"/>
+ <projects id="300" kee="pj-w-snapshot" uuid="DEFG" module_uuid="[null]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="EFGH" module_uuid="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/is_authorized_component_key_for_global_permission.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/is_authorized_component_key_for_global_permission.xml
new file mode 100644
index 00000000000..c5cd325ea5e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/is_authorized_component_key_for_global_permission.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <!-- user 100 has no direct grant access, but is in the group 200 that has the role "user"
+ on the all the projects -->
+ <user_roles id="1" user_id="100" resource_id="999" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="200" resource_id="[null]" role="user"/>
+
+ <projects id="301" kee="pj-w-snapshot:package" root_id="300" uuid="ABCD" module_uuid="DEFG"/>
+ <projects id="302" kee="pj-w-snapshot:file" root_id="300" uuid="BCDE" module_uuid="DEFG"/>
+ <projects id="303" kee="pj-w-snapshot:other" root_id="300" uuid="CDEF" module_uuid="DEFG"/>
+ <projects id="300" kee="pj-w-snapshot" uuid="DEFG" module_uuid="[null]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="EFGH" module_uuid="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_anonymous.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_anonymous.xml
new file mode 100644
index 00000000000..1c21104a7b6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_anonymous.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="[null]" resource_id="300" role="user"/>
+ <group_roles id="2" group_id="200" resource_id="400" role="codeviewer"/>
+
+ <projects id="300" kee="pj-w-snapshot" uuid="DEFG" module_uuid="[null]" enabled="[true]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="EFGH" module_uuid="[null]" enabled="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_group.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_group.xml
new file mode 100644
index 00000000000..17e6323ccd6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_group.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="200" resource_id="300" role="user"/>
+ <group_roles id="2" group_id="200" resource_id="400" role="codeviewer"/>
+
+ <projects id="300" kee="pj-w-snapshot" uuid="DEFG" module_uuid="[null]" enabled="[true]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="EFGH" module_uuid="[null]" enabled="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_user.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_user.xml
new file mode 100644
index 00000000000..515adaa8f48
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/keep_authorized_project_ids_for_user.xml
@@ -0,0 +1,10 @@
+<dataset>
+
+ <!-- user 100 has the role "user" on the project 300 -->
+ <user_roles id="1" user_id="100" resource_id="300" role="user"/>
+ <user_roles id="2" user_id="100" resource_id="400" role="codeviewer"/>
+
+ <projects id="300" kee="pj-w-snapshot" uuid="DEFG" module_uuid="[null]" enabled="[true]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="EFGH" module_uuid="[null]" enabled="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_anonymous.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_anonymous.xml
new file mode 100644
index 00000000000..515b647b270
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_anonymous.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <user_roles id="1" user_id="100" resource_id="[null]" role="user"/>
+
+ <groups_users user_id="1" group_id="200"/>
+ <groups_users user_id="1" group_id="201"/>
+
+ <group_roles id="200" group_id="[null]" resource_id="[null]" role="user"/>
+ <group_roles id="201" group_id="[null]" resource_id="[null]" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_group_anyone.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_group_anyone.xml
new file mode 100644
index 00000000000..bd166a7e669
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_global_permissions_for_group_anyone.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="10" login="anyone_user"/>
+
+ <user_roles id="1" user_id="10" resource_id="[null]" role="user"/>
+
+ <groups_users user_id="10" group_id="[null]"/>
+
+ <group_roles id="1" group_id="[null]" resource_id="[null]" role="profileadmin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_group_global_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_group_global_permissions.xml
new file mode 100644
index 00000000000..0ff49e9417c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_group_global_permissions.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <!-- user 10 has no direct grant access, but is in the 'user' group 200 and in the 'admin' group 201 -->
+ <users id="10" login="john"/>
+ <!-- user 11 has no direct grant access, but is in the 'user' group 200 -->
+ <users id="11" login="arthur"/>
+
+ <user_roles id="1" user_id="999" resource_id="[null]" role="user"/>
+ <user_roles id="2" user_id="999" resource_id="[null]" role="user"/>
+
+ <groups_users user_id="10" group_id="200"/>
+ <groups_users user_id="10" group_id="201"/>
+ <groups_users user_id="11" group_id="200"/>
+
+ <group_roles id="1" group_id="200" resource_id="[null]" role="user"/>
+ <group_roles id="2" group_id="201" resource_id="[null]" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_anonymous.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_anonymous.xml
new file mode 100644
index 00000000000..a1aa1f05a76
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_anonymous.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <user_roles id="1" user_id="100" resource_id="999" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="[null]" resource_id="300" role="user"/>
+
+ <projects id="300" uuid="ABCD" module_uuid="[null]" kee="pj-w-snapshot" scope="PRJ" qualifier="TRK" enabled="[true]"/>
+ <projects id="301" uuid="BCDE" module_uuid="[null]" kee="pj-w-snapshot1" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+ <projects id="302" uuid="CDEF" module_uuid="[null]" kee="pj-w-snapshot2" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+
+ <projects id="303" uuid="DEFG" module_uuid="[null]" kee="pj-w-snapshot3" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_group.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_group.xml
new file mode 100644
index 00000000000..93682166701
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_group.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <!-- user 100 has no direct grant access, but is in the group 200 that has the role "user"
+ on the project 300 -->
+ <user_roles id="1" user_id="100" resource_id="999" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="200" resource_id="300" role="user"/>
+
+ <projects id="300" uuid="ABCD" module_uuid="[null]" kee="pj-w-snapshot" scope="PRJ" qualifier="TRK" enabled="[true]"/>
+ <projects id="301" uuid="BCDE" module_uuid="[null]" kee="pj-w-snapshot1" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+ <projects id="302" uuid="CDEF" module_uuid="[null]" kee="pj-w-snapshot2" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+
+ <projects id="303" uuid="DEFG" module_uuid="[null]" kee="pj-w-snapshot3" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_user.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_user.xml
new file mode 100644
index 00000000000..060223cdfbd
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_root_project_keys_for_user.xml
@@ -0,0 +1,17 @@
+<dataset>
+
+ <!-- user 100 has the role "user" on the project 300 and in group 200 -->
+ <user_roles id="1" user_id="100" resource_id="300" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="200" resource_id="999" role="user"/>
+
+ <projects id="300" uuid="ABCD" module_uuid="[null]" kee="pj-w-snapshot" scope="PRJ" qualifier="TRK" enabled="[true]"/>
+ <projects id="301" uuid="BCDE" module_uuid="[null]" kee="pj-w-snapshot1" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+ <projects id="302" uuid="CDEF" module_uuid="[null]" kee="pj-w-snapshot2" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+
+ <projects id="303" uuid="DEFG" module_uuid="[null]" kee="pj-w-snapshot3" scope="PRJ" qualifier="TRK"
+ enabled="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_user_global_permissions.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_user_global_permissions.xml
new file mode 100644
index 00000000000..5b8e83ba208
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/should_return_user_global_permissions.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <!-- user 10 has no group, but has direct role 'user' and 'admin' -->
+ <users id="10" login="john"/>
+ <!-- user 11 has no group, but has direct role 'user' -->
+ <users id="11" login="arthur"/>
+
+ <user_roles id="1" user_id="10" resource_id="[null]" role="user"/>
+ <user_roles id="2" user_id="10" resource_id="[null]" role="admin"/>
+ <user_roles id="3" user_id="11" resource_id="[null]" role="user"/>
+
+ <groups_users user_id="999" group_id="200"/>
+ <groups_users user_id="999" group_id="201"/>
+
+ <group_roles id="200" group_id="200" resource_id="[null]" role="user"/>
+ <group_roles id="201" group_id="200" resource_id="[null]" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/user_should_be_authorized.xml b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/user_should_be_authorized.xml
new file mode 100644
index 00000000000..3771e09738d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/AuthorizationDaoTest/user_should_be_authorized.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <!-- user 100 has the role "user" on the project 300 and in group 200 -->
+ <user_roles id="1" user_id="100" resource_id="300" role="user"/>
+ <user_roles id="2" user_id="100" resource_id="400" role="user"/>
+ <groups_users user_id="100" group_id="200"/>
+ <group_roles id="1" group_id="200" resource_id="999" role="user"/>
+
+ <projects id="300" kee="pj-w-snapshot" uuid="DEFG" module_uuid="[null]"/>
+ <projects id="400" kee="pj-wo-snapshot" uuid="EFGH" module_uuid="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/select_user_group.xml b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/select_user_group.xml
new file mode 100644
index 00000000000..5993aae1121
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/select_user_group.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <groups id="101" name="sonar-users" description="Any new users created will automatically join this group"/>
+
+ <groups_users user_id="201" group_id="101"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared.xml b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared.xml
new file mode 100644
index 00000000000..a882f396f6c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators" description="System administrators"/>
+ <groups id="101" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="102" name="sonar-reviewers" description="Reviewers"/>
+
+ <!-- user 200 is in all groups -->
+ <groups_users user_id="200" group_id="100"/>
+ <groups_users user_id="200" group_id="101"/>
+ <groups_users user_id="200" group_id="102"/>
+
+ <!-- user 201 is in users group -->
+ <groups_users user_id="201" group_id="101"/>
+
+ <users id="200" login="two-hundred"/>
+ <users id="201" login="two-hundred-one"/>
+ <users id="202" login="two-hundred-two"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared_plus_empty_group.xml b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared_plus_empty_group.xml
new file mode 100644
index 00000000000..36b89224326
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/shared_plus_empty_group.xml
@@ -0,0 +1,20 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators" description="System administrators"/>
+ <groups id="101" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="102" name="sonar-reviewers" description="Reviewers"/>
+ <groups id="103" name="sonar-nobody" description="Nobody in this group"/>
+
+ <!-- user 200 is in all groups -->
+ <groups_users user_id="200" group_id="100"/>
+ <groups_users user_id="200" group_id="101"/>
+ <groups_users user_id="200" group_id="102"/>
+
+ <!-- user 201 is in users group -->
+ <groups_users user_id="201" group_id="101"/>
+
+ <users id="200" login="admin" name="Admin" active="[true]"/>
+ <users id="201" login="not.admin" name="Not Admin" active="[true]"/>
+ <users id="202" login="inactive" name="Inactive" active="[false]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/should_be_sorted_by_group_name.xml b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/should_be_sorted_by_group_name.xml
new file mode 100644
index 00000000000..baaecdb40d4
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/GroupMembershipDaoTest/should_be_sorted_by_group_name.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <groups id="100" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="101" name="sonar-administrators" description="System administrators"/>/>
+ <groups id="102" name="sonar-reviewers" description="Reviewers"/>
+
+ <!-- user 200 is in all groups -->
+ <groups_users user_id="200" group_id="100"/>
+ <groups_users user_id="200" group_id="101"/>
+ <groups_users user_id="200" group_id="102"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId-result.xml
new file mode 100644
index 00000000000..a9effb1ea47
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId-result.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles id="4" group_id="101" role="shareDashboard" resource_id="[null]"/>
+
+ <group_roles id="5" group_id="[null]" role="scan" resource_id="[null]"/>
+ <group_roles id="6" group_id="[null]" role="dryRunScan" resource_id="[null]"/>
+
+ <group_roles id="7" group_id="102" role="admin" resource_id="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId.xml
new file mode 100644
index 00000000000..f05b0aebecf
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/deleteGroupPermissionsByGroupId.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <!-- All 3 lines with group_id 100 will be deleted -->
+ <group_roles id="1" group_id="100" role="admin" resource_id="[null]"/>
+ <group_roles id="2" group_id="100" role="profileadmin" resource_id="1"/>
+ <group_roles id="3" group_id="100" role="shareDashboard" resource_id="[null]"/>
+
+ <group_roles id="4" group_id="101" role="shareDashboard" resource_id="[null]"/>
+
+ <group_roles id="5" group_id="[null]" role="scan" resource_id="[null]"/>
+ <group_roles id="6" group_id="[null]" role="dryRunScan" resource_id="[null]"/>
+
+ <group_roles id="7" group_id="102" role="admin" resource_id="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions-result.xml
new file mode 100644
index 00000000000..d87bfe95e07
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions-result.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles id="1" group_id="100" role="admin" resource_id="[null]"/>
+ <group_roles id="3" group_id="100" role="shareDashboard" resource_id="[null]"/>
+ <group_roles id="4" group_id="101" role="shareDashboard" resource_id="[null]"/>
+
+ <!-- Group 'anyone' has a NULL group_id -->
+ <group_roles id="5" group_id="[null]" role="scan" resource_id="[null]"/>
+ <group_roles id="6" group_id="[null]" role="dryRunScan" resource_id="[null]"/>
+
+ <group_roles id="7" group_id="102" role="admin" resource_id="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions.xml
new file mode 100644
index 00000000000..2e76afcd3be
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalGroupPermissions.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles id="1" group_id="100" role="admin" resource_id="[null]"/>
+ <group_roles id="2" group_id="100" role="profileadmin" resource_id="[null]"/>
+ <group_roles id="3" group_id="100" role="shareDashboard" resource_id="[null]"/>
+ <group_roles id="4" group_id="101" role="shareDashboard" resource_id="[null]"/>
+
+ <!-- Group 'anyone' has a NULL group_id -->
+ <group_roles id="5" group_id="[null]" role="scan" resource_id="[null]"/>
+ <group_roles id="6" group_id="[null]" role="dryRunScan" resource_id="[null]"/>
+
+ <!-- Component permission, it should not be returned with global permissions -->
+ <group_roles id="7" group_id="102" role="admin" resource_id="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions-result.xml
new file mode 100644
index 00000000000..5fd4509c330
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions-result.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="200" login="admin_user" name="admin_user" active="[true]"/>
+ <users id="201" login="profile_admin_user" name="profile_admin_user" active="[true]"/>
+
+ <user_roles id="1" user_id="200" role="admin" resource_id="[null]"/>
+ <user_roles id="3" user_id="201" role="profileadmin" resource_id="[null]"/>
+
+ <user_roles id="4" user_id="200" role="admin" resource_id="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions.xml
new file mode 100644
index 00000000000..4d95c91f03d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/globalUserPermissions.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <users id="200" login="admin_user" name="admin_user" active="[true]"/>
+ <users id="201" login="profile_admin_user" name="profile_admin_user" active="[true]"/>
+
+ <user_roles id="1" user_id="200" role="admin" resource_id="[null]"/>
+ <user_roles id="2" user_id="200" role="profileadmin" resource_id="[null]"/>
+ <user_roles id="3" user_id="201" role="profileadmin" resource_id="[null]"/>
+
+ <!-- Component permission, it should not be returned with global permissions -->
+ <user_roles id="4" user_id="200" role="admin" resource_id="1"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions-result.xml
new file mode 100644
index 00000000000..71650eb675a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles id="1" group_id="100" role="admin" resource_id="1"/>
+ <group_roles id="3" group_id="101" role="codeviewer" resource_id="1"/>
+
+ <!-- Group 'anyone' has a NULL group_id -->
+ <group_roles id="4" group_id="[null]" role="user" resource_id="1"/>
+
+ <group_roles id="5" group_id="100" role="admin" resource_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions.xml
new file mode 100644
index 00000000000..8fb07128af7
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceGroupPermissions.xml
@@ -0,0 +1,16 @@
+<dataset>
+
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+
+ <group_roles id="1" group_id="100" role="admin" resource_id="1"/>
+ <group_roles id="2" group_id="100" role="codeviewer" resource_id="1"/>
+ <group_roles id="3" group_id="101" role="codeviewer" resource_id="1"/>
+
+ <!-- Group 'anyone' has a NULL group_id -->
+ <group_roles id="4" group_id="[null]" role="user" resource_id="1"/>
+
+ <!-- Global permission, it should not be returned with component permissions -->
+ <group_roles id="5" group_id="100" role="admin" resource_id="[null]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions-result.xml
new file mode 100644
index 00000000000..7e0ac55287b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions-result.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <users id="200" login="admin_user" name="admin_user" active="[true]"/>
+ <users id="201" login="browse_admin_user" name="browse_admin_user" active="[true]"/>
+
+ <user_roles id="1" user_id="200" role="admin" resource_id="1"/>
+ <user_roles id="3" user_id="201" role="user" resource_id="1"/>
+
+ <user_roles id="4" user_id="200" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions.xml
new file mode 100644
index 00000000000..a5f72708fb6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleDaoTest/resourceUserPermissions.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <users id="200" login="admin_user" name="admin_user" active="[true]"/>
+ <users id="201" login="browse_admin_user" name="browse_admin_user" active="[true]"/>
+
+ <user_roles id="1" user_id="200" role="admin" resource_id="1"/>
+ <user_roles id="2" user_id="200" role="user" resource_id="1"/>
+ <user_roles id="3" user_id="201" role="user" resource_id="1"/>
+
+ <!-- Global permission, it should not be returned with component permissions -->
+ <user_roles id="4" user_id="200" role="admin"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/countRoles.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/countRoles.xml
new file mode 100644
index 00000000000..ec1ed3b076f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/countRoles.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="2" group_id="[null]" resource_id="123" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="123" role="codeviewer"/>
+
+ <!-- other resource -->
+ <group_roles id="3" group_id="101" resource_id="999" role="codeviewer"/>
+ <user_roles id="2" user_id="200" resource_id="999" role="codeviewer"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId-result.xml
new file mode 100644
index 00000000000..dae82ddd8f8
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <!--<group_roles id="1" group_id="100" resource_id="123" role="admin"/>-->
+ <!--<group_roles id="2" group_id="[null]" resource_id="123" role="user"/>-->
+ <!--<user_roles id="1" user_id="200" resource_id="123" role="codeviewer"/>-->
+
+ <!-- other resource -->
+ <group_roles id="3" group_id="101" resource_id="999" role="codeviewer"/>
+ <user_roles id="2" user_id="200" resource_id="999" role="codeviewer"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId.xml
new file mode 100644
index 00000000000..ec1ed3b076f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/deleteRolesByResourceId.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="2" group_id="[null]" resource_id="123" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="123" role="codeviewer"/>
+
+ <!-- other resource -->
+ <group_roles id="3" group_id="101" resource_id="999" role="codeviewer"/>
+ <user_roles id="2" user_id="200" resource_id="999" role="codeviewer"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles-result.xml
new file mode 100644
index 00000000000..7a7e6f8e196
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles-result.xml
@@ -0,0 +1,11 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+
+ <group_roles id="1" group_id="100" resource_id="123" role="admin"/>
+ <group_roles id="2" group_id="[null]" resource_id="123" role="user"/>
+ <user_roles id="1" user_id="200" resource_id="123" role="codeviewer"/>
+
+
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles.xml b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles.xml
new file mode 100644
index 00000000000..b99df83bc15
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/RoleMapperTest/insertRoles.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <groups id="100" name="sonar-administrators"/>
+ <groups id="101" name="sonar-users"/>
+ <users id="200" login="marius" name="Marius" email="[null]" active="[true]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user-result.xml
new file mode 100644
index 00000000000..60e55e6494f
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user-result.xml
@@ -0,0 +1,45 @@
+<dataset>
+ <!-- deactivated -->
+ <users id="100" login="marius" name="Marius" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1500000000000" active="[false]"/>
+
+ <!-- deleted
+ <dashboards id="1" user_id="100" NAME="[null]" COLUMN_LAYOUT="[null]" CREATED_AT="[null]" DESCRIPTION="[null]" IS_GLOBAL="[false]" SHARED="[false]" UPDATED_AT="[null]"/>
+ <active_dashboards id="1" user_id="100" dashboard_id="1" ORDER_INDEX="[null]"/>
+ <issue_filters id="1" user_login="marius" name="My issues" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]" SHARED="[false]" UPDATED_AT="[null]"/>
+ <issue_filter_favourites id="1" user_login="marius" issue_filter_id="1" CREATED_AT="[null]"/>
+ <measure_filters id="1" user_id="100" name="My measures" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]" SHARED="[false]" UPDATED_AT="[null]"/>
+ <measure_filter_favourites id="1" user_id="100" measure_filter_id="1" CREATED_AT="[null]"/>
+ <properties id="1" user_id="100" PROP_KEY="[null]" RESOURCE_ID="[null]" TEXT_VALUE="[null]" />
+ <groups_users user_id="100" group_id="200"/>
+ <user_roles id="1" user_id="100" role="admin" RESOURCE_ID="[null]"/>
+ -->
+
+ <users id="101" login="jcdus" name="Jean-Claude Dus" email="jcdus@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+ <dashboards id="2" user_id="101" NAME="[null]" COLUMN_LAYOUT="[null]" CREATED_AT="[null]" DESCRIPTION="[null]"
+ IS_GLOBAL="[false]" SHARED="[false]" UPDATED_AT="[null]"/>
+ <active_dashboards id="2" user_id="101" dashboard_id="2" ORDER_INDEX="[null]"/>
+ <active_dashboards id="4" user_id="101" dashboard_id="3" ORDER_INDEX="[null]"/>
+ <issue_filters id="2" user_login="jcdus" name="My issues" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[false]" UPDATED_AT="[null]"/>
+ <issue_filter_favourites id="2" user_login="jcdus" issue_filter_id="2" CREATED_AT="[null]"/>
+ <issue_filter_favourites id="4" user_login="jcdus" issue_filter_id="3" CREATED_AT="[null]"/>
+ <measure_filters id="2" user_id="101" name="My measures" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[false]" UPDATED_AT="[null]"/>
+ <measure_filter_favourites id="2" user_id="101" measure_filter_id="2" CREATED_AT="[null]"/>
+ <measure_filter_favourites id="4" user_id="101" measure_filter_id="3" CREATED_AT="[null]"/>
+ <properties id="2" user_id="101" PROP_KEY="[null]" RESOURCE_ID="[null]" TEXT_VALUE="[null]"/>
+ <groups_users user_id="101" group_id="200"/>
+ <user_roles id="2" user_id="101" role="admin" RESOURCE_ID="[null]"/>
+
+ <!-- Not deleted because shared -->
+ <dashboards id="3" user_id="100" NAME="[null]" COLUMN_LAYOUT="[null]" CREATED_AT="[null]" DESCRIPTION="[null]"
+ IS_GLOBAL="[false]" SHARED="[true]" UPDATED_AT="[null]"/>
+ <issue_filters id="3" user_login="marius" name="My shared issues" CREATED_AT="[null]" DATA="[null]"
+ DESCRIPTION="[null]" SHARED="[true]" UPDATED_AT="[null]"/>
+ <measure_filters id="3" user_id="100" name="My shared measures" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[true]" UPDATED_AT="[null]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user.xml
new file mode 100644
index 00000000000..804838dfa2a
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/deactivate_user.xml
@@ -0,0 +1,43 @@
+<dataset>
+ <users id="100" login="marius" name="Marius" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+ <dashboards id="1" user_id="100" NAME="[null]" COLUMN_LAYOUT="[null]" CREATED_AT="[null]" DESCRIPTION="[null]"
+ IS_GLOBAL="[false]" SHARED="[false]" UPDATED_AT="[null]"/>
+ <dashboards id="3" user_id="100" NAME="[null]" COLUMN_LAYOUT="[null]" CREATED_AT="[null]" DESCRIPTION="[null]"
+ IS_GLOBAL="[false]" SHARED="[true]" UPDATED_AT="[null]"/>
+ <active_dashboards id="1" user_id="100" dashboard_id="1" ORDER_INDEX="[null]"/>
+ <active_dashboards id="3" user_id="100" dashboard_id="3" ORDER_INDEX="[null]"/>
+ <issue_filters id="1" user_login="marius" name="My issues" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[false]" UPDATED_AT="[null]"/>
+ <issue_filters id="3" user_login="marius" name="My shared issues" CREATED_AT="[null]" DATA="[null]"
+ DESCRIPTION="[null]" SHARED="[true]" UPDATED_AT="[null]"/>
+ <issue_filter_favourites id="1" user_login="marius" issue_filter_id="1" CREATED_AT="[null]"/>
+ <issue_filter_favourites id="3" user_login="marius" issue_filter_id="3" CREATED_AT="[null]"/>
+ <measure_filters id="1" user_id="100" name="My measures" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[false]" UPDATED_AT="[null]"/>
+ <measure_filters id="3" user_id="100" name="My shared measures" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[true]" UPDATED_AT="[null]"/>
+ <measure_filter_favourites id="1" user_id="100" measure_filter_id="1" CREATED_AT="[null]"/>
+ <measure_filter_favourites id="3" user_id="100" measure_filter_id="3" CREATED_AT="[null]"/>
+ <properties id="1" user_id="100" PROP_KEY="[null]" RESOURCE_ID="[null]" TEXT_VALUE="[null]"/>
+ <groups_users user_id="100" group_id="200"/>
+ <user_roles id="1" user_id="100" role="admin" RESOURCE_ID="[null]"/>
+
+ <users id="101" login="jcdus" name="Jean-Claude Dus" email="jcdus@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+ <dashboards id="2" user_id="101" NAME="[null]" COLUMN_LAYOUT="[null]" CREATED_AT="[null]" DESCRIPTION="[null]"
+ IS_GLOBAL="[false]" SHARED="[false]" UPDATED_AT="[null]"/>
+ <active_dashboards id="2" user_id="101" dashboard_id="2" ORDER_INDEX="[null]"/>
+ <active_dashboards id="4" user_id="101" dashboard_id="3" ORDER_INDEX="[null]"/>
+ <issue_filters id="2" user_login="jcdus" name="My issues" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[false]" UPDATED_AT="[null]"/>
+ <issue_filter_favourites id="2" user_login="jcdus" issue_filter_id="2" CREATED_AT="[null]"/>
+ <issue_filter_favourites id="4" user_login="jcdus" issue_filter_id="3" CREATED_AT="[null]"/>
+ <measure_filters id="2" user_id="101" name="My measures" CREATED_AT="[null]" DATA="[null]" DESCRIPTION="[null]"
+ SHARED="[false]" UPDATED_AT="[null]"/>
+ <measure_filter_favourites id="2" user_id="101" measure_filter_id="2" CREATED_AT="[null]"/>
+ <measure_filter_favourites id="4" user_id="101" measure_filter_id="3" CREATED_AT="[null]"/>
+ <properties id="2" user_id="101" PROP_KEY="[null]" RESOURCE_ID="[null]" TEXT_VALUE="[null]"/>
+ <groups_users user_id="101" group_id="200"/>
+ <user_roles id="2" user_id="101" role="admin" RESOURCE_ID="[null]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/insert-result.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/insert-result.xml
new file mode 100644
index 00000000000..621e018f88b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/insert-result.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <users id="1" login="john" name="John" email="jo@hn.com" created_at="1418215735482" updated_at="1418215735482"
+ active="[true]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectActiveUserByLogin.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectActiveUserByLogin.xml
new file mode 100644
index 00000000000..dc029ad46d6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectActiveUserByLogin.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <!-- inactive -->
+ <users id="50" login="inactive_user" name="Disabled" email="inactive@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[false]"/>
+
+ <!-- active -->
+ <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+
+ <users id="102" login="jcdus" name="Jean-Claude Dus" email="jcdus@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectGroupByName.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectGroupByName.xml
new file mode 100644
index 00000000000..4148c50491d
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectGroupByName.xml
@@ -0,0 +1,5 @@
+<dataset>
+ <groups id="1" name="sonar-users" description="Sonar Users" created_at="2011-05-18" updated_at="2012-07-21"/>
+ <groups id="2" name="sonar-administrators" description="Sonar Administrators" created_at="2011-05-18"
+ updated_at="2012-07-21"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByLogins.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByLogins.xml
new file mode 100644
index 00000000000..3be84a27b69
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByLogins.xml
@@ -0,0 +1,9 @@
+<dataset>
+ <users id="100" login="inactive_user" name="Disabled" email="inactive@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[false]"/>
+ <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+ <users id="102" login="jcdus" name="Jean-Claude Dus" email="jcdus@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByQuery.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByQuery.xml
new file mode 100644
index 00000000000..7107ad4ed36
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByQuery.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <users id="100" login="inactive_user" name="Disabled" email="inactive@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[false]"/>
+ <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByText.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByText.xml
new file mode 100644
index 00000000000..1c685cc787c
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/selectUsersByText.xml
@@ -0,0 +1,7 @@
+<dataset>
+ <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+ <users id="102" login="sbrandhof" name="Simon Brandhof" email="marius@lesbronzes.fr" created_at="1418215735482"
+ updated_at="1418215735485" active="[true]"/>
+
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/update_user.xml b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/update_user.xml
new file mode 100644
index 00000000000..621e018f88b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/update_user.xml
@@ -0,0 +1,4 @@
+<dataset>
+ <users id="1" login="john" name="John" email="jo@hn.com" created_at="1418215735482" updated_at="1418215735482"
+ active="[true]"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion.xml b/sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion.xml
new file mode 100644
index 00000000000..867616dc080
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion.xml
@@ -0,0 +1,8 @@
+<dataset>
+
+ <schema_migrations version="1"/>
+ <schema_migrations version="2"/>
+ <schema_migrations version="4"/>
+ <schema_migrations version="123"/>
+ <schema_migrations version="50"/>
+</dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion_no_rows.xml b/sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion_no_rows.xml
new file mode 100644
index 00000000000..5ed00ba028b
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/version/DatabaseVersionTest/getVersion_no_rows.xml
@@ -0,0 +1 @@
+<dataset></dataset> \ No newline at end of file
diff --git a/sonar-db/src/test/resources/org/sonar/jpa/dao/ProfilesDaoTest/shouldGetProfiles.xml b/sonar-db/src/test/resources/org/sonar/jpa/dao/ProfilesDaoTest/shouldGetProfiles.xml
new file mode 100644
index 00000000000..aeb851beb8e
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/jpa/dao/ProfilesDaoTest/shouldGetProfiles.xml
@@ -0,0 +1,7 @@
+<dataset>
+
+ <rules_profiles id="1" name="profile one" language="java" is_default="[false]"/>
+ <rules_profiles id="2" name="profile two" language="java" is_default="[false]"/>
+ <rules_profiles id="3" name="profile three" language="plsql" is_default="[false]"/>
+
+</dataset> \ No newline at end of file