From 64daac1df29430e652e5e8f4878779705f4a1a10 Mon Sep 17 00:00:00 2001
From: Alexander Sterligov <sterligovak@joom.it>
Date: Wed, 5 Jul 2017 13:47:02 +0300
Subject: [PATCH] KYLIN-2711 avoid NPE if output is lost for 2.0.x

---
 .../org/apache/kylin/rest/service/JobService.java  | 70 ++++++++++++----------
 .../apache/kylin/rest/service/JobServiceTest.java  | 35 +++++++++++
 2 files changed, 72 insertions(+), 33 deletions(-)

diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/JobService.java b/server-base/src/main/java/org/apache/kylin/rest/service/JobService.java
index 4ba426e..8517f36 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/JobService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/JobService.java
@@ -150,49 +150,49 @@ public class JobService extends BasicService implements InitializingBean {
 
     private long getTimeStartInMillis(Calendar calendar, JobTimeFilterEnum timeFilter) {
         switch (timeFilter) {
-        case LAST_ONE_DAY:
-            calendar.add(Calendar.DAY_OF_MONTH, -1);
-            return calendar.getTimeInMillis();
-        case LAST_ONE_WEEK:
-            calendar.add(Calendar.WEEK_OF_MONTH, -1);
-            return calendar.getTimeInMillis();
-        case LAST_ONE_MONTH:
-            calendar.add(Calendar.MONTH, -1);
-            return calendar.getTimeInMillis();
-        case LAST_ONE_YEAR:
-            calendar.add(Calendar.YEAR, -1);
-            return calendar.getTimeInMillis();
-        case ALL:
-            return 0;
-        default:
-            throw new RuntimeException("illegal timeFilter for job history:" + timeFilter);
+            case LAST_ONE_DAY:
+                calendar.add(Calendar.DAY_OF_MONTH, -1);
+                return calendar.getTimeInMillis();
+            case LAST_ONE_WEEK:
+                calendar.add(Calendar.WEEK_OF_MONTH, -1);
+                return calendar.getTimeInMillis();
+            case LAST_ONE_MONTH:
+                calendar.add(Calendar.MONTH, -1);
+                return calendar.getTimeInMillis();
+            case LAST_ONE_YEAR:
+                calendar.add(Calendar.YEAR, -1);
+                return calendar.getTimeInMillis();
+            case ALL:
+                return 0;
+            default:
+                throw new RuntimeException("illegal timeFilter for job history:" + timeFilter);
         }
     }
 
     private ExecutableState parseToExecutableState(JobStatusEnum status) {
         switch (status) {
-        case DISCARDED:
-            return ExecutableState.DISCARDED;
-        case ERROR:
-            return ExecutableState.ERROR;
-        case FINISHED:
-            return ExecutableState.SUCCEED;
-        case NEW:
-            return ExecutableState.READY;
-        case PENDING:
-            return ExecutableState.READY;
-        case RUNNING:
-            return ExecutableState.RUNNING;
-        case STOPPED:
-            return ExecutableState.STOPPED;
-        default:
-            throw new RuntimeException("illegal status:" + status);
+            case DISCARDED:
+                return ExecutableState.DISCARDED;
+            case ERROR:
+                return ExecutableState.ERROR;
+            case FINISHED:
+                return ExecutableState.SUCCEED;
+            case NEW:
+                return ExecutableState.READY;
+            case PENDING:
+                return ExecutableState.READY;
+            case RUNNING:
+                return ExecutableState.RUNNING;
+            case STOPPED:
+                return ExecutableState.STOPPED;
+            default:
+                throw new RuntimeException("illegal status:" + status);
         }
     }
 
     @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'OPERATION') or hasPermission(#cube, 'MANAGEMENT')")
     public JobInstance submitJob(CubeInstance cube, long startDate, long endDate, long startOffset, long endOffset, //
-            Map<Integer, Long> sourcePartitionOffsetStart, Map<Integer, Long> sourcePartitionOffsetEnd, CubeBuildTypeEnum buildType, boolean force, String submitter) throws IOException, JobException {
+                                 Map<Integer, Long> sourcePartitionOffsetStart, Map<Integer, Long> sourcePartitionOffsetEnd, CubeBuildTypeEnum buildType, boolean force, String submitter) throws IOException, JobException {
 
         if (cube.getStatus() == RealizationStatusEnum.DESCBROKEN) {
             throw new BadRequestException("Broken cube " + cube.getName() + " can't be built");
@@ -323,6 +323,7 @@ public class JobService extends BasicService implements InitializingBean {
 
     /**
      * currently only support substring match
+     *
      * @return
      */
     public List<JobInstance> searchJobs(final String cubeNameSubstring, final String projectName, final List<JobStatusEnum> statusList, final Integer limitValue, final Integer offsetValue, final JobTimeFilterEnum timeFilter) throws IOException, JobException {
@@ -396,6 +397,9 @@ public class JobService extends BasicService implements InitializingBean {
             public boolean apply(CubingJob executable) {
                 try {
                     Output output = allOutputs.get(executable.getId());
+                    if (output == null) {
+                        return false;
+                    }
                     ExecutableState state = output.getState();
                     boolean ret = statusList.contains(state);
                     return ret;
diff --git a/server/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java b/server/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
index 4150808..ea1fbbb 100644
--- a/server/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
+++ b/server/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
@@ -19,13 +19,19 @@
 package org.apache.kylin.rest.service;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
 
+import org.apache.kylin.engine.mr.CubingJob;
 import org.apache.kylin.job.constant.JobTimeFilterEnum;
+import org.apache.kylin.job.exception.ExecuteException;
 import org.apache.kylin.job.exception.JobException;
+import org.apache.kylin.job.execution.*;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.junit.Assert;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 
 /**
  * @author xduo
@@ -33,9 +39,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 public class JobServiceTest extends ServiceTestBase {
 
     @Autowired
+    @Qualifier("jobService")
     JobService jobService;
 
     @Autowired
+    @Qualifier("cacheService")
     private CacheService cacheService;
 
     @Test
@@ -47,4 +55,31 @@ public class JobServiceTest extends ServiceTestBase {
         Assert.assertNull(jobService.getJobInstance("job_not_exist"));
         Assert.assertNotNull(jobService.searchJobs(null, null, null, 0, 0, JobTimeFilterEnum.ALL));
     }
+
+    @Test
+    public void testExceptionOnLostJobOutput() {
+        ExecutableManager manager = ExecutableManager.getInstance(jobService.getConfig());
+        AbstractExecutable executable = new TestJob();
+        manager.addJob(executable);
+        List<CubingJob> jobs = jobService.searchCubingJobs("cube",
+                "jobName",
+                Collections.<ExecutableState>emptySet(),
+                0,
+                Long.MAX_VALUE,
+                Collections.<String, Output>emptyMap(),
+                true);
+        Assert.assertEquals(0, jobs.size());
+    }
+
+    public static class TestJob extends CubingJob {
+
+        public TestJob(){
+            super();
+        }
+
+        @Override
+        protected ExecuteResult doWork(ExecutableContext context) throws ExecuteException {
+            return new ExecuteResult(ExecuteResult.State.SUCCEED, "");
+        }
+    }
 }
-- 
2.7.4

