]> source.dussan.org Git - sonarqube.git/commitdiff
Fix some quality flaws
authorJulien HENRY <julien.henry@sonarsource.com>
Mon, 26 Jan 2015 10:21:26 +0000 (11:21 +0100)
committerJulien HENRY <julien.henry@sonarsource.com>
Mon, 26 Jan 2015 10:21:44 +0000 (11:21 +0100)
19 files changed:
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/issues/PreviousIssue.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapContainer.java
sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java
sonar-batch/src/main/java/org/sonar/batch/index/EventPersister.java
sonar-batch/src/main/java/org/sonar/batch/index/SourcePersister.java
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueRepository.java
sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsProvider.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java
sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectReferentialsLoaderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java

index f519286ff4a851fec4509af0a448e3f1cd21fb12..d4d8a5ca7c95982139ebae1d0daec7d7bf20f3fb 100644 (file)
@@ -24,6 +24,9 @@ import javax.annotation.Nullable;
 import java.io.Serializable;
 import java.util.Date;
 
+/**
+ * Issues from previous analysis. USed by local issue tracking.
+ */
 public class PreviousIssue implements Serializable {
 
   private String key;
index d3749c4cf7f16d211cea435e8b3467452953d46a..c49e4c1fef11bc9cb08e850142cf054dde4bdae4 100644 (file)
@@ -35,11 +35,11 @@ import org.sonar.batch.components.PastSnapshotFinderByDays;
 import org.sonar.batch.components.PastSnapshotFinderByPreviousAnalysis;
 import org.sonar.batch.components.PastSnapshotFinderByPreviousVersion;
 import org.sonar.batch.components.PastSnapshotFinderByVersion;
-import org.sonar.batch.repository.DefaultGlobalReferentialsLoader;
+import org.sonar.batch.repository.DefaultGlobalRepositoriesLoader;
 import org.sonar.batch.repository.DefaultPreviousIssuesLoader;
-import org.sonar.batch.repository.DefaultProjectReferentialsLoader;
-import org.sonar.batch.repository.GlobalReferentialsLoader;
-import org.sonar.batch.repository.GlobalReferentialsProvider;
+import org.sonar.batch.repository.DefaultProjectRepositoriesLoader;
+import org.sonar.batch.repository.GlobalRepositoriesLoader;
+import org.sonar.batch.repository.GlobalRepositoriesProvider;
 import org.sonar.batch.repository.PreviousIssuesLoader;
 import org.sonar.batch.repository.ProjectRepositoriesLoader;
 import org.sonar.batch.user.UserRepository;
@@ -107,16 +107,16 @@ public class BootstrapContainer extends ComponentContainer {
       UriReader.class,
       new FileCacheProvider(),
       System2.INSTANCE,
-      new GlobalReferentialsProvider(),
+      new GlobalRepositoriesProvider(),
       UserRepository.class);
     if (getComponentByType(PluginsReferential.class) == null) {
       add(DefaultPluginsReferential.class);
     }
-    if (getComponentByType(GlobalReferentialsLoader.class) == null) {
-      add(DefaultGlobalReferentialsLoader.class);
+    if (getComponentByType(GlobalRepositoriesLoader.class) == null) {
+      add(DefaultGlobalRepositoriesLoader.class);
     }
     if (getComponentByType(ProjectRepositoriesLoader.class) == null) {
-      add(DefaultProjectReferentialsLoader.class);
+      add(DefaultProjectRepositoriesLoader.class);
     }
     if (getComponentByType(PreviousIssuesLoader.class) == null) {
       add(DefaultPreviousIssuesLoader.class);
index 455257aab8d6fe0edb06bc11ded2db1d168f8134..f6cc8e660a6738dff152fcaa3b9c7e1f197ded83 100644 (file)
@@ -25,6 +25,8 @@ import org.sonar.api.CoreProperties;
 import org.sonar.api.config.Settings;
 import org.sonar.api.measures.Metric;
 
+import javax.annotation.Nullable;
+
 public class SqaleRatingSettings implements BatchComponent {
 
   private final Settings settings;
@@ -48,11 +50,13 @@ public class SqaleRatingSettings implements BatchComponent {
     }
   }
 
-  public long getDevCost(String languageKey) {
+  public long getDevCost(@Nullable String languageKey) {
     try {
-      LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
-      if (languageSpecificConfig != null && languageSpecificConfig.getManDays() != null) {
-        return Long.parseLong(languageSpecificConfig.getManDays());
+      if (languageKey != null) {
+        LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+        if (languageSpecificConfig != null && languageSpecificConfig.getManDays() != null) {
+          return Long.parseLong(languageSpecificConfig.getManDays());
+        }
       }
       return Long.parseLong(settings.getString(CoreProperties.DEVELOPMENT_COST));
     } catch (Exception e) {
@@ -61,10 +65,12 @@ public class SqaleRatingSettings implements BatchComponent {
     }
   }
 
-  public Metric getSizeMetric(String languageKey, Metric[] metrics) {
-    LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
-    if (languageSpecificConfig != null && languageSpecificConfig.getMetric() != null) {
-      return getMetricForKey(languageSpecificConfig.getMetric(), metrics);
+  public Metric getSizeMetric(@Nullable String languageKey, Metric[] metrics) {
+    if (languageKey != null) {
+      LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+      if (languageSpecificConfig != null && languageSpecificConfig.getMetric() != null) {
+        return getMetricForKey(languageSpecificConfig.getMetric(), metrics);
+      }
     }
     return getMetricForKey(settings.getString(CoreProperties.SIZE_METRIC), metrics);
   }
index 36ac996ae581b73fe464a23f3f2c6499715bcf4b..b01efa10063668887aa730845c9debb56d2bf99c 100644 (file)
@@ -45,6 +45,9 @@ public class EventPersister {
 
   public void saveEvent(Resource resource, Event event) {
     BatchResource batchResource = resourceCache.get(resource.getEffectiveKey());
+    if (batchResource == null) {
+      throw new IllegalStateException("Unknow component: " + resource);
+    }
     if (event.getDate() == null) {
       event.setSnapshot(batchResource.snapshot());
     } else {
index 88f6ac854c4de306147b01161d567e18262562e6..512e13be07e2210b606d3bd58164f8e404f336d3 100644 (file)
@@ -294,8 +294,9 @@ public class SourcePersister implements ScanPersister {
   @CheckForNull
   private SyntaxHighlightingData loadHighlighting(DefaultInputFile file) {
     SyntaxHighlightingData highlighting = componentDataCache.getData(file.key(), SnapshotDataTypes.SYNTAX_HIGHLIGHTING);
-    if (highlighting == null) {
-      highlighting = codeColorizers.toSyntaxHighlighting(file.file(), file.encoding(), file.language());
+    String language = file.language();
+    if (highlighting == null && language != null) {
+      highlighting = codeColorizers.toSyntaxHighlighting(file.file(), file.encoding(), language);
     }
     return highlighting;
   }
index 6bad06cf818c2c51e2262a37e684ac7a88b4a35f..d1142691ba37103c114f1f88751340144cb928fa 100644 (file)
@@ -33,6 +33,8 @@ import org.sonar.batch.index.ResourceCache;
 import org.sonar.batch.protocol.input.issues.PreviousIssue;
 import org.sonar.batch.repository.PreviousIssuesLoader;
 
+import javax.annotation.Nullable;
+
 @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
 public class PreviousIssueRepository implements BatchComponent {
 
@@ -58,7 +60,10 @@ public class PreviousIssueRepository implements BatchComponent {
       previousIssuesLoader.load(reactor, new Function<PreviousIssue, Void>() {
 
         @Override
-        public Void apply(PreviousIssue issue) {
+        public Void apply(@Nullable PreviousIssue issue) {
+          if (issue == null) {
+            return null;
+          }
           String componentKey = issue.componentKey();
           BatchResource r = resourceCache.get(componentKey);
           if (r == null) {
index 80f524401521b4bbf39f3af379bc2824066ef464..e5ac2e3046f7a6a12ac77820e4d8e6183dae566c 100644 (file)
@@ -35,7 +35,7 @@ import org.sonar.batch.protocol.input.ActiveRule;
 import org.sonar.batch.protocol.input.GlobalRepositories;
 import org.sonar.batch.protocol.input.ProjectRepositories;
 import org.sonar.batch.protocol.input.issues.PreviousIssue;
-import org.sonar.batch.repository.GlobalReferentialsLoader;
+import org.sonar.batch.repository.GlobalRepositoriesLoader;
 import org.sonar.batch.repository.PreviousIssuesLoader;
 import org.sonar.batch.repository.ProjectRepositoriesLoader;
 import org.sonar.core.plugins.DefaultPluginMetadata;
@@ -194,7 +194,7 @@ public class BatchMediumTester {
     }
   }
 
-  private static class FakeGlobalReferentialsLoader implements GlobalReferentialsLoader {
+  private static class FakeGlobalReferentialsLoader implements GlobalRepositoriesLoader {
 
     private int metricId = 1;
 
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalReferentialsLoader.java
deleted file mode 100644 (file)
index 409e776..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.repository;
-
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.protocol.input.GlobalRepositories;
-
-public class DefaultGlobalReferentialsLoader implements GlobalReferentialsLoader {
-
-  private static final String BATCH_GLOBAL_URL = "/batch/global";
-
-  private final ServerClient serverClient;
-
-  public DefaultGlobalReferentialsLoader(ServerClient serverClient) {
-    this.serverClient = serverClient;
-  }
-
-  @Override
-  public GlobalRepositories load() {
-    return GlobalRepositories.fromJson(serverClient.request(BATCH_GLOBAL_URL));
-  }
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java
new file mode 100644 (file)
index 0000000..428e9bc
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.repository;
+
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.protocol.input.GlobalRepositories;
+
+public class DefaultGlobalRepositoriesLoader implements GlobalRepositoriesLoader {
+
+  private static final String BATCH_GLOBAL_URL = "/batch/global";
+
+  private final ServerClient serverClient;
+
+  public DefaultGlobalRepositoriesLoader(ServerClient serverClient) {
+    this.serverClient = serverClient;
+  }
+
+  @Override
+  public GlobalRepositories load() {
+    return GlobalRepositories.fromJson(serverClient.request(BATCH_GLOBAL_URL));
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectReferentialsLoader.java
deleted file mode 100644 (file)
index d653570..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.repository;
-
-import com.google.common.collect.Maps;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.database.model.MeasureModel;
-import org.sonar.api.database.model.ResourceModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectRepositories;
-import org.sonar.batch.rule.ModuleQProfiles;
-
-import javax.annotation.CheckForNull;
-import javax.persistence.NoResultException;
-import javax.persistence.Query;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-public class DefaultProjectReferentialsLoader implements ProjectRepositoriesLoader {
-
-  private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectReferentialsLoader.class);
-
-  private static final String BATCH_PROJECT_URL = "/batch/project";
-
-  private final ServerClient serverClient;
-  private final AnalysisMode analysisMode;
-  private final DatabaseSession session;
-
-  public DefaultProjectReferentialsLoader(DatabaseSession session, ServerClient serverClient, AnalysisMode analysisMode) {
-    this.session = session;
-    this.serverClient = serverClient;
-    this.analysisMode = analysisMode;
-  }
-
-  public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode) {
-    this.session = null;
-    this.serverClient = serverClient;
-    this.analysisMode = analysisMode;
-  }
-
-  @Override
-  public ProjectRepositories load(ProjectReactor reactor, TaskProperties taskProperties) {
-    String projectKey = reactor.getRoot().getKeyWithBranch();
-    String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey);
-    if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
-      LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP
-        + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server.");
-      url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP));
-    }
-    url += "&preview=" + analysisMode.isPreview();
-    ProjectRepositories ref = ProjectRepositories.fromJson(serverClient.request(url));
-
-    if (session != null) {
-      for (ProjectDefinition module : reactor.getProjects()) {
-
-        for (Entry<String, FileData> fileDataByPaths : ref.fileDataByPath(module.getKeyWithBranch()).entrySet()) {
-          String path = fileDataByPaths.getKey();
-          FileData fileData = fileDataByPaths.getValue();
-          String lastCommits = null;
-          String revisions = null;
-          String authors = null;
-          List<Object[]> measuresByKey = query(projectKey + ":" + path, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, CoreMetrics.SCM_REVISIONS_BY_LINE_KEY,
-            CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
-          for (Object[] measureByKey : measuresByKey) {
-            if (measureByKey[0].equals(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)) {
-              lastCommits = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
-            } else if (measureByKey[0].equals(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY)) {
-              revisions = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
-            } else if (measureByKey[0].equals(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)) {
-              authors = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
-            }
-          }
-          ref.addFileData(module.getKeyWithBranch(), path, new FileData(fileData.hash(), authors == null, lastCommits, revisions, authors));
-        }
-      }
-      ref.setLastAnalysisDate(lastSnapshotCreationDate(projectKey));
-    }
-    return ref;
-  }
-
-  public List<Object[]> query(String resourceKey, String... metricKeys) {
-    StringBuilder sb = new StringBuilder();
-    Map<String, Object> params = Maps.newHashMap();
-
-    sb.append("SELECT met.key, m");
-    sb.append(" FROM ")
-      .append(MeasureModel.class.getSimpleName())
-      .append(" m, ")
-      .append(Metric.class.getSimpleName())
-      .append(" met, ")
-      .append(ResourceModel.class.getSimpleName())
-      .append(" r, ")
-      .append(Snapshot.class.getSimpleName())
-      .append(" s WHERE met.id=m.metricId AND m.snapshotId=s.id AND s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
-    params.put("kee", resourceKey);
-    params.put("status", Snapshot.STATUS_PROCESSED);
-    params.put("lib", Qualifiers.LIBRARY);
-
-    sb.append(" AND m.characteristicId IS NULL");
-    sb.append(" AND m.personId IS NULL");
-    sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
-    if (metricKeys.length > 0) {
-      sb.append(" AND met.key IN (:metricKeys) ");
-      params.put("metricKeys", Arrays.asList(metricKeys));
-    }
-    sb.append(" AND s.last=true ");
-    sb.append(" ORDER BY s.createdAt ");
-
-    Query jpaQuery = session.createQuery(sb.toString());
-
-    for (Map.Entry<String, Object> entry : params.entrySet()) {
-      jpaQuery.setParameter(entry.getKey(), entry.getValue());
-    }
-    return jpaQuery.getResultList();
-  }
-
-  @CheckForNull
-  Date lastSnapshotCreationDate(String resourceKey) {
-    StringBuilder sb = new StringBuilder();
-    Map<String, Object> params = Maps.newHashMap();
-
-    sb.append("SELECT s.buildDate");
-    sb.append(" FROM ")
-      .append(ResourceModel.class.getSimpleName())
-      .append(" r, ")
-      .append(Snapshot.class.getSimpleName())
-      .append(" s WHERE s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
-    params.put("kee", resourceKey);
-    params.put("status", Snapshot.STATUS_PROCESSED);
-    params.put("lib", Qualifiers.LIBRARY);
-
-    sb.append(" AND s.last=true ");
-
-    Query jpaQuery = session.createQuery(sb.toString());
-
-    for (Map.Entry<String, Object> entry : params.entrySet()) {
-      jpaQuery.setParameter(entry.getKey(), entry.getValue());
-    }
-    try {
-      return (Date) jpaQuery.getSingleResult();
-    } catch (NoResultException e) {
-      return null;
-    }
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java
new file mode 100644 (file)
index 0000000..922e9a8
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.repository;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.batch.bootstrap.AnalysisMode;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.FileData;
+import org.sonar.batch.protocol.input.ProjectRepositories;
+import org.sonar.batch.rule.ModuleQProfiles;
+
+import javax.annotation.CheckForNull;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoader {
+
+  private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectRepositoriesLoader.class);
+
+  private static final String BATCH_PROJECT_URL = "/batch/project";
+
+  private final ServerClient serverClient;
+  private final AnalysisMode analysisMode;
+  private final DatabaseSession session;
+
+  public DefaultProjectRepositoriesLoader(DatabaseSession session, ServerClient serverClient, AnalysisMode analysisMode) {
+    this.session = session;
+    this.serverClient = serverClient;
+    this.analysisMode = analysisMode;
+  }
+
+  public DefaultProjectRepositoriesLoader(ServerClient serverClient, AnalysisMode analysisMode) {
+    this.session = null;
+    this.serverClient = serverClient;
+    this.analysisMode = analysisMode;
+  }
+
+  @Override
+  public ProjectRepositories load(ProjectReactor reactor, TaskProperties taskProperties) {
+    String projectKey = reactor.getRoot().getKeyWithBranch();
+    String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey);
+    if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
+      LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP
+        + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server.");
+      url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP));
+    }
+    url += "&preview=" + analysisMode.isPreview();
+    ProjectRepositories ref = ProjectRepositories.fromJson(serverClient.request(url));
+
+    if (session != null) {
+      for (ProjectDefinition module : reactor.getProjects()) {
+
+        for (Entry<String, FileData> fileDataByPaths : ref.fileDataByPath(module.getKeyWithBranch()).entrySet()) {
+          String path = fileDataByPaths.getKey();
+          FileData fileData = fileDataByPaths.getValue();
+          String lastCommits = null;
+          String revisions = null;
+          String authors = null;
+          List<Object[]> measuresByKey = query(projectKey + ":" + path, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, CoreMetrics.SCM_REVISIONS_BY_LINE_KEY,
+            CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
+          for (Object[] measureByKey : measuresByKey) {
+            if (measureByKey[0].equals(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)) {
+              lastCommits = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
+            } else if (measureByKey[0].equals(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY)) {
+              revisions = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
+            } else if (measureByKey[0].equals(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)) {
+              authors = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
+            }
+          }
+          ref.addFileData(module.getKeyWithBranch(), path, new FileData(fileData.hash(), authors == null, lastCommits, revisions, authors));
+        }
+      }
+      ref.setLastAnalysisDate(lastSnapshotCreationDate(projectKey));
+    }
+    return ref;
+  }
+
+  public List<Object[]> query(String resourceKey, String... metricKeys) {
+    StringBuilder sb = new StringBuilder();
+    Map<String, Object> params = Maps.newHashMap();
+
+    sb.append("SELECT met.key, m");
+    sb.append(" FROM ")
+      .append(MeasureModel.class.getSimpleName())
+      .append(" m, ")
+      .append(Metric.class.getSimpleName())
+      .append(" met, ")
+      .append(ResourceModel.class.getSimpleName())
+      .append(" r, ")
+      .append(Snapshot.class.getSimpleName())
+      .append(" s WHERE met.id=m.metricId AND m.snapshotId=s.id AND s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
+    params.put("kee", resourceKey);
+    params.put("status", Snapshot.STATUS_PROCESSED);
+    params.put("lib", Qualifiers.LIBRARY);
+
+    sb.append(" AND m.characteristicId IS NULL");
+    sb.append(" AND m.personId IS NULL");
+    sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
+    if (metricKeys.length > 0) {
+      sb.append(" AND met.key IN (:metricKeys) ");
+      params.put("metricKeys", Arrays.asList(metricKeys));
+    }
+    sb.append(" AND s.last=true ");
+    sb.append(" ORDER BY s.createdAt ");
+
+    Query jpaQuery = session.createQuery(sb.toString());
+
+    for (Map.Entry<String, Object> entry : params.entrySet()) {
+      jpaQuery.setParameter(entry.getKey(), entry.getValue());
+    }
+    return jpaQuery.getResultList();
+  }
+
+  @CheckForNull
+  Date lastSnapshotCreationDate(String resourceKey) {
+    StringBuilder sb = new StringBuilder();
+    Map<String, Object> params = Maps.newHashMap();
+
+    sb.append("SELECT s.buildDate");
+    sb.append(" FROM ")
+      .append(ResourceModel.class.getSimpleName())
+      .append(" r, ")
+      .append(Snapshot.class.getSimpleName())
+      .append(" s WHERE s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
+    params.put("kee", resourceKey);
+    params.put("status", Snapshot.STATUS_PROCESSED);
+    params.put("lib", Qualifiers.LIBRARY);
+
+    sb.append(" AND s.last=true ");
+
+    Query jpaQuery = session.createQuery(sb.toString());
+
+    for (Map.Entry<String, Object> entry : params.entrySet()) {
+      jpaQuery.setParameter(entry.getKey(), entry.getValue());
+    }
+    try {
+      return (Date) jpaQuery.getSingleResult();
+    } catch (NoResultException e) {
+      return null;
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsLoader.java
deleted file mode 100644 (file)
index 1d10182..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.repository;
-
-import org.sonar.batch.protocol.input.GlobalRepositories;
-
-public interface GlobalReferentialsLoader {
-
-  GlobalRepositories load();
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsProvider.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsProvider.java
deleted file mode 100644 (file)
index eddee9c..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.repository;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.utils.TimeProfiler;
-import org.sonar.batch.protocol.input.GlobalRepositories;
-
-public class GlobalReferentialsProvider extends ProviderAdapter {
-
-  private static final Logger LOG = LoggerFactory.getLogger(GlobalReferentialsProvider.class);
-
-  private GlobalRepositories globalReferentials;
-
-  public GlobalRepositories provide(GlobalReferentialsLoader loader) {
-    if (globalReferentials == null) {
-      TimeProfiler profiler = new TimeProfiler(LOG).start("Load global referentials");
-      try {
-        globalReferentials = loader.load();
-      } finally {
-        profiler.stop();
-      }
-    }
-    return globalReferentials;
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesLoader.java
new file mode 100644 (file)
index 0000000..e7f5651
--- /dev/null
@@ -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.batch.repository;
+
+import org.sonar.batch.protocol.input.GlobalRepositories;
+
+public interface GlobalRepositoriesLoader {
+
+  GlobalRepositories load();
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java
new file mode 100644 (file)
index 0000000..6f13f38
--- /dev/null
@@ -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.batch.repository;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.protocol.input.GlobalRepositories;
+
+public class GlobalRepositoriesProvider extends ProviderAdapter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(GlobalRepositoriesProvider.class);
+
+  private GlobalRepositories globalReferentials;
+
+  public GlobalRepositories provide(GlobalRepositoriesLoader loader) {
+    if (globalReferentials == null) {
+      TimeProfiler profiler = new TimeProfiler(LOG).start("Load global repositories");
+      try {
+        globalReferentials = loader.load();
+      } finally {
+        profiler.stop();
+      }
+    }
+    return globalReferentials;
+  }
+}
index 390f81786af90f2a7088a589208617ce0b7ccb8f..11e9510ea8b260dbbf40ed760658f8e1014c26c0 100644 (file)
@@ -35,7 +35,7 @@ public class ProjectRepositoriesProvider extends ProviderAdapter {
 
   public ProjectRepositories provide(ProjectRepositoriesLoader loader, ProjectReactor reactor, TaskProperties taskProps) {
     if (projectReferentials == null) {
-      TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials");
+      TimeProfiler profiler = new TimeProfiler(LOG).start("Load project repositories");
       try {
         projectReferentials = loader.load(reactor, taskProps);
       } finally {
diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectReferentialsLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectReferentialsLoaderTest.java
deleted file mode 100644 (file)
index 108151f..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.repository;
-
-import org.sonar.batch.repository.DefaultProjectReferentialsLoader;
-
-import com.google.common.collect.Maps;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.rule.ModuleQProfiles;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DefaultProjectReferentialsLoaderTest {
-
-  private DefaultProjectReferentialsLoader loader;
-  private ServerClient serverClient;
-  private AnalysisMode analysisMode;
-  private ProjectReactor reactor;
-  private TaskProperties taskProperties;
-
-  @Before
-  public void prepare() {
-    serverClient = mock(ServerClient.class);
-    analysisMode = mock(AnalysisMode.class);
-    loader = new DefaultProjectReferentialsLoader(mock(DatabaseSession.class), serverClient, analysisMode);
-    loader = spy(loader);
-    doReturn(null).when(loader).lastSnapshotCreationDate(anyString());
-    when(serverClient.request(anyString())).thenReturn("{}");
-    taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
-  }
-
-  @Test
-  public void passPreviewParameter() {
-    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
-    when(analysisMode.isPreview()).thenReturn(false);
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo&preview=false");
-
-    when(analysisMode.isPreview()).thenReturn(true);
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo&preview=true");
-  }
-
-  @Test
-  public void passAndEncodeProjectKeyParameter() {
-    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr"));
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false");
-  }
-
-  @Test
-  public void passAndEncodeProfileParameter() {
-    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
-    taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2");
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false");
-  }
-
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java
new file mode 100644 (file)
index 0000000..27e07d7
--- /dev/null
@@ -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.batch.repository;
+
+import org.sonar.batch.repository.DefaultProjectRepositoriesLoader;
+
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.batch.bootstrap.AnalysisMode;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.rule.ModuleQProfiles;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultProjectRepositoriesLoaderTest {
+
+  private DefaultProjectRepositoriesLoader loader;
+  private ServerClient serverClient;
+  private AnalysisMode analysisMode;
+  private ProjectReactor reactor;
+  private TaskProperties taskProperties;
+
+  @Before
+  public void prepare() {
+    serverClient = mock(ServerClient.class);
+    analysisMode = mock(AnalysisMode.class);
+    loader = new DefaultProjectRepositoriesLoader(mock(DatabaseSession.class), serverClient, analysisMode);
+    loader = spy(loader);
+    doReturn(null).when(loader).lastSnapshotCreationDate(anyString());
+    when(serverClient.request(anyString())).thenReturn("{}");
+    taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
+  }
+
+  @Test
+  public void passPreviewParameter() {
+    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
+    when(analysisMode.isPreview()).thenReturn(false);
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo&preview=false");
+
+    when(analysisMode.isPreview()).thenReturn(true);
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo&preview=true");
+  }
+
+  @Test
+  public void passAndEncodeProjectKeyParameter() {
+    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr"));
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false");
+  }
+
+  @Test
+  public void passAndEncodeProfileParameter() {
+    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
+    taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2");
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false");
+  }
+
+}
index ecfd73199af46a91a16868069876690ee90860e5..393e69331fac433b53e550a4c4d56e68ecc92e2e 100644 (file)
@@ -22,6 +22,9 @@ package org.sonar.api.batch.fs.internal;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.utils.PathUtils;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
 import java.io.File;
 import java.io.Serializable;
 
@@ -67,6 +70,7 @@ public class DefaultInputFile implements InputFile, Serializable {
     return new File(absolutePath);
   }
 
+  @CheckForNull
   @Override
   public String language() {
     return language;
@@ -129,7 +133,7 @@ public class DefaultInputFile implements InputFile, Serializable {
     return this;
   }
 
-  public DefaultInputFile setLanguage(String language) {
+  public DefaultInputFile setLanguage(@Nullable String language) {
     this.language = language;
     return this;
   }