@@ -25,6 +25,9 @@ import com.google.common.base.Splitter; | |||
import com.google.common.base.Strings; | |||
import com.google.common.collect.Collections2; | |||
import com.google.common.collect.Lists; | |||
import java.time.Clock; | |||
import java.time.OffsetDateTime; | |||
import java.time.Period; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
@@ -40,12 +43,9 @@ import java.util.stream.Collectors; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang.BooleanUtils; | |||
import org.joda.time.DateTime; | |||
import org.joda.time.format.ISOPeriodFormat; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
@@ -90,12 +90,12 @@ public class IssueQueryFactory { | |||
private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setProjectUuid(UNKNOWN); | |||
private final DbClient dbClient; | |||
private final System2 system; | |||
private final Clock clock; | |||
private final UserSession userSession; | |||
public IssueQueryFactory(DbClient dbClient, System2 system, UserSession userSession) { | |||
public IssueQueryFactory(DbClient dbClient, Clock clock, UserSession userSession) { | |||
this.dbClient = dbClient; | |||
this.system = system; | |||
this.clock = clock; | |||
this.userSession = userSession; | |||
} | |||
@@ -138,8 +138,10 @@ public class IssueQueryFactory { | |||
Date actualCreatedAfter = createdAfter; | |||
if (createdInLast != null) { | |||
actualCreatedAfter = new DateTime(system.now()).minus( | |||
ISOPeriodFormat.standard().parsePeriod("P" + createdInLast.toUpperCase(Locale.ENGLISH))).toDate(); | |||
actualCreatedAfter = Date.from( | |||
OffsetDateTime.now(clock) | |||
.minus(Period.parse("P" + createdInLast.toUpperCase(Locale.ENGLISH))) | |||
.toInstant()); | |||
} | |||
return actualCreatedAfter; | |||
} | |||
@@ -392,7 +394,7 @@ public class IssueQueryFactory { | |||
return mainBranchProjectUuid == null ? componentDto.projectUuid() : mainBranchProjectUuid; | |||
} | |||
private static void setBranch(IssueQuery.Builder builder, ComponentDto component, @Nullable String branch){ | |||
private static void setBranch(IssueQuery.Builder builder, ComponentDto component, @Nullable String branch) { | |||
builder.branchUuid(branch == null ? null : component.projectUuid()); | |||
builder.mainBranch(branch == null || !branch.equals(component.getBranch())); | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.server.platform.platformlevel; | |||
import java.time.Clock; | |||
import java.util.Properties; | |||
import javax.annotation.Nullable; | |||
import org.sonar.NetworkUtils; | |||
@@ -101,6 +102,7 @@ public class PlatformLevel1 extends PlatformLevel { | |||
TempFolderCleaner.class, | |||
new TempFolderProvider(), | |||
System2.INSTANCE, | |||
Clock.systemDefaultZone(), | |||
// user session | |||
ThreadLocalUserSession.class, |
@@ -19,6 +19,8 @@ | |||
*/ | |||
package org.sonar.server.issue; | |||
import java.time.Clock; | |||
import java.time.ZoneOffset; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
import org.junit.Rule; | |||
@@ -27,7 +29,6 @@ import org.junit.rules.ExpectedException; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.SnapshotDto; | |||
@@ -59,8 +60,8 @@ public class IssueQueryFactoryTest { | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private System2 system = mock(System2.class); | |||
private IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), system, userSession); | |||
private Clock clock = mock(Clock.class); | |||
private IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), clock, userSession); | |||
@Test | |||
public void create_from_parameters() { | |||
@@ -126,6 +127,26 @@ public class IssueQueryFactoryTest { | |||
assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18")); | |||
} | |||
@Test | |||
public void creation_date_support_localdate() { | |||
SearchWsRequest request = new SearchWsRequest() | |||
.setCreatedAt("2013-04-16"); | |||
IssueQuery query = underTest.create(request); | |||
assertThat(query.createdAt()).isEqualTo(DateUtils.parseDate("2013-04-16")); | |||
} | |||
@Test | |||
public void creation_date_support_zoneddatetime() { | |||
SearchWsRequest request = new SearchWsRequest() | |||
.setCreatedAt("2013-04-16T09:08:24+0200"); | |||
IssueQuery query = underTest.create(request); | |||
assertThat(query.createdAt()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200")); | |||
} | |||
@Test | |||
public void add_unknown_when_no_component_found() { | |||
SearchWsRequest request = new SearchWsRequest() | |||
@@ -403,8 +424,8 @@ public class IssueQueryFactoryTest { | |||
.setComponentKeys(singletonList(file.getKey())) | |||
.setBranch(branch.getBranch()) | |||
.setOnComponentOnly(true))) | |||
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch) | |||
.containsOnly(branch.uuid(), singletonList(file.uuid()), false); | |||
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch) | |||
.containsOnly(branch.uuid(), singletonList(file.uuid()), false); | |||
} | |||
@Test | |||
@@ -415,13 +436,13 @@ public class IssueQueryFactoryTest { | |||
assertThat(underTest.create(new SearchWsRequest() | |||
.setProjectKeys(singletonList(project.getKey())) | |||
.setBranch("master"))) | |||
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) | |||
.containsOnly(project.uuid(), singletonList(project.uuid()), true); | |||
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) | |||
.containsOnly(project.uuid(), singletonList(project.uuid()), true); | |||
assertThat(underTest.create(new SearchWsRequest() | |||
.setComponentKeys(singletonList(project.getKey())) | |||
.setBranch("master"))) | |||
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) | |||
.containsOnly(project.uuid(), singletonList(project.uuid()), true); | |||
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) | |||
.containsOnly(project.uuid(), singletonList(project.uuid()), true); | |||
} | |||
@Test | |||
@@ -441,7 +462,8 @@ public class IssueQueryFactoryTest { | |||
@Test | |||
public void set_created_after_from_created_since() { | |||
Date now = DateUtils.parseDateTime("2013-07-25T07:35:00+0100"); | |||
when(system.now()).thenReturn(now.getTime()); | |||
when(clock.instant()).thenReturn(now.toInstant()); | |||
when(clock.getZone()).thenReturn(ZoneOffset.UTC); | |||
SearchWsRequest request = new SearchWsRequest() | |||
.setCreatedInLast("1y2m3w4d"); | |||
assertThat(underTest.create(request).createdAfter()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100")); |
@@ -20,6 +20,7 @@ | |||
package org.sonar.server.issue.ws; | |||
import java.io.IOException; | |||
import java.time.Clock; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import org.junit.Rule; | |||
@@ -93,7 +94,7 @@ public class SearchActionComponentsTest { | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)); | |||
private ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client()); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, System2.INSTANCE, userSession); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession); | |||
private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter(); | |||
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter); | |||
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new ActionFinder(userSession), |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.server.issue.ws; | |||
import java.time.Clock; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -95,10 +96,11 @@ public class SearchActionTest { | |||
private DbSession session = db.getSession(); | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new AuthorizationTypeSupport(userSessionRule)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, System2.INSTANCE, userSessionRule); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSessionRule); | |||
private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter(); | |||
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter); | |||
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSessionRule, dbClient, new ActionFinder(userSessionRule), new TransitionService(userSessionRule, issueWorkflow)); | |||
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSessionRule, dbClient, new ActionFinder(userSessionRule), | |||
new TransitionService(userSessionRule, issueWorkflow)); | |||
private Languages languages = new Languages(); | |||
private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl()); | |||
private WsActionTester ws = new WsActionTester(new SearchAction(userSessionRule, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat)); |
@@ -24,7 +24,6 @@ import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.rule.RuleStatus; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.SonarException; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
@@ -132,7 +131,7 @@ public class RubyUtilsTest { | |||
@Test | |||
public void toDate_bad_format() { | |||
throwable.expect(SonarException.class); | |||
throwable.expect(RuntimeException.class); | |||
RubyUtils.toDate("01/02/2013"); | |||
} |
@@ -32,7 +32,6 @@ import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.SonarException; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.lang.String.format; | |||
@@ -246,7 +245,7 @@ public abstract class Request { | |||
try { | |||
return DateUtils.parseDate(s); | |||
} catch (SonarException notDateException) { | |||
} catch (RuntimeException notDateException) { | |||
throw new IllegalArgumentException(notDateException); | |||
} | |||
} |
@@ -19,15 +19,13 @@ | |||
*/ | |||
package org.sonar.api.utils; | |||
import java.io.NotSerializableException; | |||
import java.io.ObjectInputStream; | |||
import java.io.ObjectOutputStream; | |||
import java.lang.ref.Reference; | |||
import java.lang.ref.SoftReference; | |||
import java.text.DateFormat; | |||
import java.text.FieldPosition; | |||
import java.text.ParsePosition; | |||
import java.text.SimpleDateFormat; | |||
import java.time.Instant; | |||
import java.time.LocalDate; | |||
import java.time.OffsetDateTime; | |||
import java.time.ZoneId; | |||
import java.time.format.DateTimeFormatter; | |||
import java.time.format.DateTimeParseException; | |||
import java.time.temporal.ChronoUnit; | |||
import java.util.Date; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
@@ -44,26 +42,44 @@ public final class DateUtils { | |||
public static final String DATE_FORMAT = "yyyy-MM-dd"; | |||
public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; | |||
private static final ThreadSafeDateFormat THREAD_SAFE_DATE_FORMAT = new ThreadSafeDateFormat(DATE_FORMAT); | |||
private static final ThreadSafeDateFormat THREAD_SAFE_DATETIME_FORMAT = new ThreadSafeDateFormat(DATETIME_FORMAT); | |||
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT); | |||
private DateUtils() { | |||
} | |||
/** | |||
* Warning: relies on default timezone! | |||
*/ | |||
public static String formatDate(Date d) { | |||
return THREAD_SAFE_DATE_FORMAT.format(d); | |||
return d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString(); | |||
} | |||
/** | |||
* Warning: relies on default timezone! | |||
*/ | |||
public static String formatDateTime(Date d) { | |||
return THREAD_SAFE_DATETIME_FORMAT.format(d); | |||
return formatDateTime(OffsetDateTime.ofInstant(d.toInstant(), ZoneId.systemDefault())); | |||
} | |||
/** | |||
* Warning: relies on default timezone! | |||
*/ | |||
public static String formatDateTime(long ms) { | |||
return THREAD_SAFE_DATETIME_FORMAT.format(new Date(ms)); | |||
return formatDateTime(OffsetDateTime.ofInstant(Instant.ofEpochMilli(ms), ZoneId.systemDefault())); | |||
} | |||
/** | |||
* @since 6.6 | |||
*/ | |||
public static String formatDateTime(OffsetDateTime dt) { | |||
return DATETIME_FORMATTER.format(dt); | |||
} | |||
/** | |||
* Warning: relies on default timezone! | |||
*/ | |||
public static String formatDateTimeNullSafe(@Nullable Date date) { | |||
return date == null ? "" : THREAD_SAFE_DATETIME_FORMAT.format(date); | |||
return date == null ? "" : formatDateTime(date); | |||
} | |||
@CheckForNull | |||
@@ -77,16 +93,12 @@ public final class DateUtils { | |||
} | |||
/** | |||
* Return a date at the start of day. | |||
* @param s string in format {@link #DATE_FORMAT} | |||
* @throws SonarException when string cannot be parsed | |||
*/ | |||
public static Date parseDate(String s) { | |||
ParsePosition pos = new ParsePosition(0); | |||
Date result = THREAD_SAFE_DATE_FORMAT.parse(s, pos); | |||
if (pos.getIndex() != s.length()) { | |||
throw new SonarException("The date '" + s + "' does not respect format '" + DATE_FORMAT + "'"); | |||
} | |||
return result; | |||
return Date.from(parseLocalDate(s).atStartOfDay(ZoneId.systemDefault()).toInstant()); | |||
} | |||
/** | |||
@@ -110,18 +122,57 @@ public final class DateUtils { | |||
return date; | |||
} | |||
/** | |||
* @since 6.6 | |||
*/ | |||
public static LocalDate parseLocalDate(String s) { | |||
try { | |||
return LocalDate.parse(s); | |||
} catch (DateTimeParseException e) { | |||
throw MessageException.of("The date '" + s + "' does not respect format '" + DATE_FORMAT + "'", e); | |||
} | |||
} | |||
/** | |||
* Parse format {@link #DATE_FORMAT}. This method never throws exception. | |||
* | |||
* @param s any string | |||
* @return the date, {@code null} if parsing error or if parameter is {@code null} | |||
* @since 6.6 | |||
*/ | |||
@CheckForNull | |||
public static LocalDate parseLocalDateQuietly(@Nullable String s) { | |||
LocalDate date = null; | |||
if (s != null) { | |||
try { | |||
date = parseLocalDate(s); | |||
} catch (RuntimeException e) { | |||
// ignore | |||
} | |||
} | |||
return date; | |||
} | |||
/** | |||
* @param s string in format {@link #DATETIME_FORMAT} | |||
* @throws SonarException when string cannot be parsed | |||
*/ | |||
public static Date parseDateTime(String s) { | |||
ParsePosition pos = new ParsePosition(0); | |||
Date result = THREAD_SAFE_DATETIME_FORMAT.parse(s, pos); | |||
if (pos.getIndex() != s.length()) { | |||
throw new SonarException("The date '" + s + "' does not respect format '" + DATETIME_FORMAT + "'"); | |||
return Date.from(parseOffsetDateTime(s).toInstant()); | |||
} | |||
/** | |||
* @param s string in format {@link #DATETIME_FORMAT} | |||
* @throws SonarException when string cannot be parsed | |||
* @since 6.6 | |||
*/ | |||
public static OffsetDateTime parseOffsetDateTime(String s) { | |||
try { | |||
return OffsetDateTime.parse(s, DATETIME_FORMATTER); | |||
} catch (DateTimeParseException e) { | |||
throw MessageException.of("The date '" + s + "' does not respect format '" + DATETIME_FORMAT + "'", e); | |||
} | |||
return result; | |||
} | |||
/** | |||
@@ -145,6 +196,28 @@ public final class DateUtils { | |||
} | |||
/** | |||
* Parse format {@link #DATETIME_FORMAT}. This method never throws exception. | |||
* | |||
* @param s any string | |||
* @return the datetime, {@code null} if parsing error or if parameter is {@code null} | |||
* @since 6.6 | |||
*/ | |||
@CheckForNull | |||
public static OffsetDateTime parseOffsetDateTimeQuietly(@Nullable String s) { | |||
OffsetDateTime datetime = null; | |||
if (s != null) { | |||
try { | |||
datetime = parseOffsetDateTime(s); | |||
} catch (RuntimeException e) { | |||
// ignore | |||
} | |||
} | |||
return datetime; | |||
} | |||
/** | |||
* Warning: may rely on default timezone! | |||
* @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime | |||
* @return the datetime, {@code null} if stringDate is null | |||
* @since 6.1 | |||
@@ -155,18 +228,19 @@ public final class DateUtils { | |||
return null; | |||
} | |||
Date date = parseDateTimeQuietly(stringDate); | |||
if (date != null) { | |||
return date; | |||
OffsetDateTime odt = parseOffsetDateTimeQuietly(stringDate); | |||
if (odt != null) { | |||
return Date.from(odt.toInstant()); | |||
} | |||
date = parseDateQuietly(stringDate); | |||
checkArgument(date != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate); | |||
LocalDate ld = parseLocalDateQuietly(stringDate); | |||
checkArgument(ld != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate); | |||
return date; | |||
return Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()); | |||
} | |||
/** | |||
* Warning: may rely on default timezone! | |||
* @see #parseDateOrDateTime(String) | |||
*/ | |||
@CheckForNull | |||
@@ -176,7 +250,7 @@ public final class DateUtils { | |||
/** | |||
* Return the datetime if @param stringDate is a datetime, date + 1 day if stringDate is a date. | |||
* So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00+0000' in GMT | |||
* So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00+0000' in GMT (Warning: relies on default timezone!) | |||
* @see #parseDateOrDateTime(String) | |||
* @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime | |||
* @return the datetime, {@code null} if stringDate is null | |||
@@ -208,49 +282,7 @@ public final class DateUtils { | |||
* @return the new date object with the amount added | |||
*/ | |||
public static Date addDays(Date date, int numberOfDays) { | |||
return org.apache.commons.lang.time.DateUtils.addDays(date, numberOfDays); | |||
return Date.from(date.toInstant().plus(numberOfDays, ChronoUnit.DAYS)); | |||
} | |||
static class ThreadSafeDateFormat extends DateFormat { | |||
private final String format; | |||
private final ThreadLocal<Reference<DateFormat>> cache = new ThreadLocal<Reference<DateFormat>>() { | |||
@Override | |||
public Reference<DateFormat> get() { | |||
Reference<DateFormat> softRef = super.get(); | |||
if (softRef == null || softRef.get() == null) { | |||
SimpleDateFormat sdf = new SimpleDateFormat(format); | |||
sdf.setLenient(false); | |||
softRef = new SoftReference<>(sdf); | |||
super.set(softRef); | |||
} | |||
return softRef; | |||
} | |||
}; | |||
ThreadSafeDateFormat(String format) { | |||
this.format = format; | |||
} | |||
private DateFormat getDateFormat() { | |||
return cache.get().get(); | |||
} | |||
@Override | |||
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { | |||
return getDateFormat().format(date, toAppendTo, fieldPosition); | |||
} | |||
@Override | |||
public Date parse(String source, ParsePosition pos) { | |||
return getDateFormat().parse(source, pos); | |||
} | |||
private void readObject(ObjectInputStream ois) throws NotSerializableException { | |||
throw new NotSerializableException(); | |||
} | |||
private void writeObject(ObjectOutputStream ois) throws NotSerializableException { | |||
throw new NotSerializableException(); | |||
} | |||
} | |||
} |
@@ -20,6 +20,7 @@ | |||
package org.sonar.api.utils; | |||
import java.net.URL; | |||
import java.time.Clock; | |||
import java.util.Date; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
@@ -72,7 +73,9 @@ public class System2 { | |||
/** | |||
* Shortcut for {@link System#currentTimeMillis()} | |||
* @deprecated since 6.6 use {@link Clock} that is available in pico. | |||
*/ | |||
@Deprecated | |||
public long now() { | |||
return System.currentTimeMillis(); | |||
} |
@@ -22,16 +22,13 @@ package org.sonar.api.utils; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
import java.util.List; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.runner.RunWith; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Fail.fail; | |||
import static org.sonar.api.utils.DateUtils.parseDate; | |||
import static org.sonar.api.utils.DateUtils.parseDateOrDateTime; | |||
import static org.sonar.api.utils.DateUtils.parseDateTime; | |||
@@ -52,13 +49,13 @@ public class DateUtilsTest { | |||
@Test | |||
public void parseDate_not_valid_format() { | |||
expectedException.expect(SonarException.class); | |||
expectedException.expect(MessageException.class); | |||
DateUtils.parseDate("2010/05/18"); | |||
} | |||
@Test | |||
public void parseDate_not_lenient() { | |||
expectedException.expect(SonarException.class); | |||
expectedException.expect(MessageException.class); | |||
DateUtils.parseDate("2010-13-18"); | |||
} | |||
@@ -71,7 +68,7 @@ public class DateUtilsTest { | |||
@Test | |||
public void parseDate_fail_if_additional_characters() { | |||
expectedException.expect(SonarException.class); | |||
expectedException.expect(MessageException.class); | |||
DateUtils.parseDate("1986-12-04foo"); | |||
} | |||
@@ -83,13 +80,13 @@ public class DateUtilsTest { | |||
@Test | |||
public void parseDateTime_not_valid_format() { | |||
expectedException.expect(SonarException.class); | |||
expectedException.expect(MessageException.class); | |||
DateUtils.parseDate("2010/05/18 10:55"); | |||
} | |||
@Test | |||
public void parseDateTime_fail_if_additional_characters() { | |||
expectedException.expect(SonarException.class); | |||
expectedException.expect(MessageException.class); | |||
DateUtils.parseDateTime("1986-12-04T01:02:03+0300foo"); | |||
} | |||
@@ -103,7 +100,7 @@ public class DateUtilsTest { | |||
@Test | |||
public void shouldFormatDate() { | |||
assertThat(DateUtils.formatDate(new Date())).startsWith("20"); | |||
assertThat(DateUtils.formatDate(new Date()).length()).isEqualTo(10); | |||
assertThat(DateUtils.formatDate(new Date())).hasSize(10); | |||
} | |||
@Test | |||
@@ -194,80 +191,4 @@ public class DateUtilsTest { | |||
parseEndingDateOrDateTime("polop"); | |||
} | |||
/** | |||
* Cordially copied from XStream unit test | |||
* See http://koders.com/java/fid8A231D75F2C6E6909FB26BCA11C12D08AD05FB50.aspx?s=ThreadSafeDateFormatTest | |||
*/ | |||
@Test | |||
public void shouldBeThreadSafe() throws Exception { | |||
final DateUtils.ThreadSafeDateFormat format = new DateUtils.ThreadSafeDateFormat("yyyy-MM-dd'T'HH:mm:ss,S z"); | |||
final Date now = new Date(); | |||
final List<Throwable> throwables = new ArrayList<>(); | |||
final ThreadGroup tg = new ThreadGroup("shouldBeThreadSafe") { | |||
@Override | |||
public void uncaughtException(Thread t, Throwable e) { | |||
throwables.add(e); | |||
super.uncaughtException(t, e); | |||
} | |||
}; | |||
final int[] counter = new int[1]; | |||
counter[0] = 0; | |||
final Thread[] threads = new Thread[10]; | |||
for (int i = 0; i < threads.length; ++i) { | |||
threads[i] = new Thread(tg, "JUnit Thread " + i) { | |||
@Override | |||
public void run() { | |||
int i = 0; | |||
try { | |||
synchronized (this) { | |||
notifyAll(); | |||
wait(); | |||
} | |||
while (i < 1000 && !interrupted()) { | |||
String formatted = format.format(now); | |||
Thread.yield(); | |||
assertThat(now).isEqualTo(format.parse(formatted)); | |||
++i; | |||
} | |||
} catch (Exception e) { | |||
fail("Unexpected exception: " + e); | |||
} | |||
synchronized (counter) { | |||
counter[0] += i; | |||
} | |||
} | |||
}; | |||
} | |||
for (int i = 0; i < threads.length; ++i) { | |||
synchronized (threads[i]) { | |||
threads[i].start(); | |||
threads[i].wait(); | |||
} | |||
} | |||
for (int i = 0; i < threads.length; ++i) { | |||
synchronized (threads[i]) { | |||
threads[i].notifyAll(); | |||
} | |||
} | |||
Thread.sleep(1000); | |||
for (int i = 0; i < threads.length; ++i) { | |||
threads[i].interrupt(); | |||
} | |||
for (int i = 0; i < threads.length; ++i) { | |||
synchronized (threads[i]) { | |||
threads[i].join(); | |||
} | |||
} | |||
assertThat(throwables).isEmpty(); | |||
assertThat(counter[0]).isGreaterThanOrEqualTo(threads.length); | |||
} | |||
} |
@@ -19,16 +19,14 @@ | |||
*/ | |||
package org.sonar.scanner; | |||
import java.time.Clock; | |||
import java.util.Date; | |||
import java.util.Optional; | |||
import org.picocontainer.Startable; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.batch.ScannerSide; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.SonarException; | |||
import org.sonar.api.utils.System2; | |||
/** | |||
* @since 6.3 | |||
@@ -37,15 +35,15 @@ import org.sonar.api.utils.System2; | |||
*/ | |||
@ScannerSide | |||
public class ProjectAnalysisInfo implements Startable { | |||
private final System2 system2; | |||
private final Clock clock; | |||
private Configuration settings; | |||
private Date analysisDate; | |||
private String analysisVersion; | |||
public ProjectAnalysisInfo(Configuration settings, System2 system2) { | |||
public ProjectAnalysisInfo(Configuration settings, Clock clock) { | |||
this.settings = settings; | |||
this.system2 = system2; | |||
this.clock = clock; | |||
} | |||
public Date analysisDate() { | |||
@@ -59,15 +57,19 @@ public class ProjectAnalysisInfo implements Startable { | |||
private Date loadAnalysisDate() { | |||
Optional<String> value = settings.get(CoreProperties.PROJECT_DATE_PROPERTY); | |||
if (!value.isPresent()) { | |||
return new Date(system2.now()); | |||
return Date.from(clock.instant()); | |||
} | |||
Date date; | |||
try { | |||
// sonar.projectDate may have been specified as a time | |||
return DateUtils.parseDateTime(value.get()); | |||
} catch (SonarException e) { | |||
} catch (RuntimeException e) { | |||
// this is probably just a date | |||
} | |||
try { | |||
// sonar.projectDate may have been specified as a date | |||
return DateUtils.parseDate(value.get()); | |||
} catch (RuntimeException e) { | |||
throw new IllegalArgumentException("Illegal value for '" + CoreProperties.PROJECT_DATE_PROPERTY + "'", e); | |||
} | |||
} | |||
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.scanner.bootstrap; | |||
import java.time.Clock; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.sonar.api.Plugin; | |||
@@ -94,6 +95,7 @@ public class GlobalContainer extends ComponentContainer { | |||
UriReader.class, | |||
new FileCacheProvider(), | |||
System2.INSTANCE, | |||
Clock.systemDefaultZone(), | |||
new MetricsRepositoryProvider(), | |||
UuidFactoryImpl.INSTANCE); | |||
addIfMissing(ScannerPluginInstaller.class, PluginInstaller.class); |
@@ -19,29 +19,62 @@ | |||
*/ | |||
package org.sonar.scanner; | |||
import java.time.Clock; | |||
import java.time.LocalDate; | |||
import java.time.OffsetDateTime; | |||
import java.time.ZoneId; | |||
import java.time.ZoneOffset; | |||
import java.util.Date; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.utils.System2; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
public class ProjectAnalysisInfoTest { | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
@Test | |||
public void testSimpleDateTime() { | |||
MapSettings settings = new MapSettings(); | |||
settings.appendProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2017-01-01T12:13:14+0200"); | |||
settings.appendProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "version"); | |||
Clock clock = mock(Clock.class); | |||
ProjectAnalysisInfo info = new ProjectAnalysisInfo(settings.asConfig(), clock); | |||
info.start(); | |||
OffsetDateTime date = OffsetDateTime.of(2017, 1, 1, 12, 13, 14, 0, ZoneOffset.ofHours(2)); | |||
assertThat(info.analysisDate()).isEqualTo(Date.from(date.toInstant())); | |||
assertThat(info.analysisVersion()).isEqualTo("version"); | |||
} | |||
@Test | |||
public void testSimpleDate() { | |||
MapSettings settings = new MapSettings(); | |||
settings.appendProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2017-01-01"); | |||
settings.appendProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "version"); | |||
System2 system = mock(System2.class); | |||
ProjectAnalysisInfo info = new ProjectAnalysisInfo(settings.asConfig(), system); | |||
Clock clock = mock(Clock.class); | |||
ProjectAnalysisInfo info = new ProjectAnalysisInfo(settings.asConfig(), clock); | |||
info.start(); | |||
LocalDate date = LocalDate.of(2017, 1, 1); | |||
assertThat(info.analysisDate()).isEqualTo(Date.from(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); | |||
assertThat(info.analysisVersion()).isEqualTo("version"); | |||
} | |||
@Test | |||
public void emptyDate() { | |||
MapSettings settings = new MapSettings(); | |||
settings.appendProperty(CoreProperties.PROJECT_DATE_PROPERTY, ""); | |||
settings.appendProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "version"); | |||
Clock clock = mock(Clock.class); | |||
ProjectAnalysisInfo info = new ProjectAnalysisInfo(settings.asConfig(), clock); | |||
thrown.expect(RuntimeException.class); | |||
info.start(); | |||
} | |||
} |
@@ -21,6 +21,7 @@ package org.sonar.scanner.repository; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
import java.util.List; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
@@ -28,6 +29,7 @@ import org.junit.Test; | |||
import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.sonar.api.batch.bootstrap.ProjectKey; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.log.LogTester; | |||
import org.sonar.api.utils.log.LoggerLevel; | |||
import org.sonar.scanner.analysis.AnalysisProperties; | |||
@@ -69,7 +71,7 @@ public class QualityProfileProviderTest { | |||
when(projectRepo.exists()).thenReturn(true); | |||
response = new ArrayList<>(1); | |||
response.add(QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").build()); | |||
response.add(QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").setRulesUpdatedAt(DateUtils.formatDateTime(new Date())).build()); | |||
} | |||
@Test |
@@ -20,6 +20,7 @@ | |||
package org.sonar.scanner.rule; | |||
import com.google.common.collect.ImmutableList; | |||
import java.util.Date; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import org.junit.Before; | |||
@@ -28,6 +29,7 @@ import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
@@ -79,7 +81,7 @@ public class ActiveRulesProviderTest { | |||
List<QualityProfile> profiles = new LinkedList<>(); | |||
for (String k : keys) { | |||
QualityProfile p = QualityProfile.newBuilder().setKey(k).setLanguage(k).build(); | |||
QualityProfile p = QualityProfile.newBuilder().setKey(k).setLanguage(k).setRulesUpdatedAt(DateUtils.formatDateTime(new Date())).build(); | |||
profiles.add(p); | |||
} | |||