You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by lu...@apache.org on 2015/01/07 15:46:25 UTC

[04/51] [partial] incubator-kylin git commit: migrate repo from github.com to apache git

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/CubeController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/CubeController.java b/server/src/main/java/com/kylinolap/rest/controller/CubeController.java
new file mode 100644
index 0000000..d952ef4
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/CubeController.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kylinolap.rest.controller;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.codahale.metrics.annotation.Metered;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.kylinolap.common.util.JsonUtil;
+import com.kylinolap.cube.CubeBuildTypeEnum;
+import com.kylinolap.cube.CubeInstance;
+import com.kylinolap.cube.CubeSegment;
+import com.kylinolap.cube.exception.CubeIntegrityException;
+import com.kylinolap.cube.project.ProjectInstance;
+import com.kylinolap.job.JobInstance;
+import com.kylinolap.job.exception.InvalidJobInstanceException;
+import com.kylinolap.job.exception.JobException;
+import com.kylinolap.metadata.model.cube.CubeDesc;
+import com.kylinolap.rest.exception.BadRequestException;
+import com.kylinolap.rest.exception.ForbiddenException;
+import com.kylinolap.rest.exception.InternalErrorException;
+import com.kylinolap.rest.exception.NotFoundException;
+import com.kylinolap.rest.request.CubeRequest;
+import com.kylinolap.rest.request.JobBuildRequest;
+import com.kylinolap.rest.response.GeneralResponse;
+import com.kylinolap.rest.response.HBaseResponse;
+import com.kylinolap.rest.service.CubeService;
+import com.kylinolap.rest.service.JobService;
+import com.kylinolap.storage.hbase.observer.CoprocessorEnabler;
+
+/**
+ * CubeController is defined as Restful API entrance for UI.
+ * 
+ * @author jianliu
+ */
+@Controller
+@RequestMapping(value = "/cubes")
+public class CubeController extends BasicController {
+    private static final Logger logger = LoggerFactory.getLogger(CubeController.class);
+
+    @Autowired
+    private CubeService cubeService;
+
+    @Autowired
+    private JobService jobService;
+
+    @RequestMapping(value = "", method = { RequestMethod.GET })
+    @ResponseBody
+    @Metered(name = "listCubes")
+    public List<CubeInstance> getCubes(@RequestParam(value = "cubeName", required = false) String cubeName, @RequestParam(value = "projectName", required = false) String projectName, @RequestParam("limit") Integer limit, @RequestParam("offset") Integer offset) {
+        return cubeService.getCubes(cubeName, projectName, (null == limit) ? 20 : limit, offset);
+    }
+
+    /**
+     * Get hive SQL of the cube
+     * 
+     * @param cubeName
+     *            Cube Name
+     * @return
+     * @throws UnknownHostException
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{cubeName}/segs/{segmentName}/sql", method = { RequestMethod.GET })
+    @ResponseBody
+    public GeneralResponse getSql(@PathVariable String cubeName, @PathVariable String segmentName) {
+        String sql = null;
+        try {
+            sql = cubeService.getJobManager().previewFlatHiveQL(cubeName, segmentName);
+        } catch (JobException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        } catch (UnknownHostException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+
+        GeneralResponse repsonse = new GeneralResponse();
+        repsonse.setProperty("sql", sql);
+
+        return repsonse;
+    }
+
+    /**
+     * Update cube notify list
+     * 
+     * @param cubeName
+     * @param notifyList
+     * @throws IOException
+     * @throws CubeIntegrityException
+     */
+    @RequestMapping(value = "/{cubeName}/notify_list", method = { RequestMethod.PUT })
+    @ResponseBody
+    public void updateNotifyList(@PathVariable String cubeName, @RequestBody List<String> notifyList) {
+        CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+
+        if (cube == null) {
+            throw new InternalErrorException("Cannot find cube " + cubeName);
+        }
+
+        try {
+            cubeService.updateCubeNotifyList(cube, notifyList);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+
+    }
+
+    @RequestMapping(value = "/{cubeName}/cost", method = { RequestMethod.PUT })
+    @ResponseBody
+    @Metered(name = "updateCubeCost")
+    public CubeInstance updateCubeCost(@PathVariable String cubeName, @RequestParam(value = "cost") int cost) {
+        try {
+            return cubeService.updateCubeCost(cubeName, cost);
+        } catch (Exception e) {
+            String message = "Failed to update cube cost: " + cubeName + " : " + cost;
+            logger.error(message, e);
+            throw new InternalErrorException(message + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+    @RequestMapping(value = "/{cubeName}/coprocessor", method = { RequestMethod.PUT })
+    @ResponseBody
+    public Map<String, Boolean> updateCubeCoprocessor(@PathVariable String cubeName, @RequestParam(value = "force") String force) {
+        try {
+            CoprocessorEnabler.updateCubeOverride(cubeName, force);
+            return CoprocessorEnabler.getCubeOverrides();
+        } catch (Exception e) {
+            String message = "Failed to update cube coprocessor: " + cubeName + " : " + force;
+            logger.error(message, e);
+            throw new InternalErrorException(message + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Force rebuild a cube's lookup table snapshot
+     * 
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{cubeName}/segs/{segmentName}/refresh_lookup", method = { RequestMethod.PUT })
+    @ResponseBody
+    public CubeInstance rebuildLookupSnapshot(@PathVariable String cubeName, @PathVariable String segmentName, @RequestParam(value = "lookupTable") String lookupTable) {
+        try {
+            return cubeService.rebuildLookupSnapshot(cubeName, segmentName, lookupTable);
+        } catch (IOException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * Send a rebuild cube job
+     * 
+     * @param cubeName
+     *            Cube ID
+     * @return
+     * @throws SchedulerException
+     * @throws IOException
+     * @throws InvalidJobInstanceException
+     */
+    @RequestMapping(value = "/{cubeName}/rebuild", method = { RequestMethod.PUT })
+    @ResponseBody
+    public JobInstance rebuild(@PathVariable String cubeName, @RequestBody JobBuildRequest jobBuildRequest) {
+        JobInstance jobInstance = null;
+        try {
+            CubeInstance cube = jobService.getCubeManager().getCube(cubeName);
+            
+            String submitter = SecurityContextHolder.getContext().getAuthentication().getName();
+
+            String jobId = jobService.submitJob(cube, jobBuildRequest.getStartTime(), jobBuildRequest.getEndTime(), CubeBuildTypeEnum.valueOf(jobBuildRequest.getBuildType()),submitter);
+            jobInstance = jobService.getJobInstance(jobId);
+        } catch (JobException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        } catch (IOException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        } catch (InvalidJobInstanceException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+
+        return jobInstance;
+    }
+
+    @RequestMapping(value = "/{cubeName}/disable", method = { RequestMethod.PUT })
+    @ResponseBody
+    @Metered(name = "disableCube")
+    public CubeInstance disableCube(@PathVariable String cubeName) {
+        try {
+            CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+
+            if (cube == null) {
+                throw new InternalErrorException("Cannot find cube " + cubeName);
+            }
+
+            return cubeService.disableCube(cube);
+        } catch (Exception e) {
+            String message = "Failed to disable cube: " + cubeName;
+            logger.error(message, e);
+            throw new InternalErrorException(message + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+    @RequestMapping(value = "/{cubeName}/purge", method = { RequestMethod.PUT })
+    @ResponseBody
+    @Metered(name = "purgeCube")
+    public CubeInstance purgeCube(@PathVariable String cubeName) {
+        try {
+            CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+
+            if (cube == null) {
+                throw new InternalErrorException("Cannot find cube " + cubeName);
+            }
+
+            return cubeService.purgeCube(cube);
+        } catch (Exception e) {
+            String message = "Failed to purge cube: " + cubeName;
+            logger.error(message, e);
+            throw new InternalErrorException(message + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+
+    @RequestMapping(value = "/{cubeName}/enable", method = { RequestMethod.PUT })
+    @ResponseBody
+    @Metered(name = "enableCube")
+    public CubeInstance enableCube(@PathVariable String cubeName) {
+        try {
+            CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+            if (null == cube) {
+                throw new InternalErrorException("Cannot find cube " + cubeName);
+            }
+
+            return cubeService.enableCube(cube);
+        } catch (Exception e) {
+            String message = "Failed to enable cube: " + cubeName;
+            logger.error(message, e);
+            throw new InternalErrorException(message + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+    @RequestMapping(value = "/{cubeName}", method = { RequestMethod.DELETE })
+    @ResponseBody
+    @Metered(name = "deleteCube")
+    public void deleteCube(@PathVariable String cubeName) {
+        CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+        if (null == cube) {
+            throw new NotFoundException("Cube with name " + cubeName + " not found..");
+        }
+
+        try {
+            cubeService.deleteCube(cube);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException("Failed to delete cube. " + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Get available table list of the input database
+     * 
+     * @return Table metadata array
+     * @throws IOException
+     */
+    @RequestMapping(value = "", method = { RequestMethod.POST })
+    @ResponseBody
+    @Metered(name = "saveCube")
+    public CubeRequest saveCubeDesc(@RequestBody CubeRequest cubeRequest) {
+        CubeDesc desc = deserializeCubeDesc(cubeRequest);
+        if (desc == null) {
+            return cubeRequest;
+        }
+
+        String name = CubeService.getCubeNameFromDesc(desc.getName());
+        if (StringUtils.isEmpty(name)) {
+            logger.info("Cube name should not be empty.");
+            throw new BadRequestException("Cube name should not be empty.");
+        }
+
+        try {
+            desc.setUuid(UUID.randomUUID().toString());
+            String projectName = (null == cubeRequest.getProject()) ? ProjectInstance.DEFAULT_PROJECT_NAME : cubeRequest.getProject();
+            cubeService.createCubeAndDesc(name, projectName, desc);
+        } catch (Exception e) {
+            logger.error("Failed to deal with the request.", e);
+            throw new InternalErrorException(e.getLocalizedMessage(),e);
+        }
+
+        cubeRequest.setUuid(desc.getUuid());
+        cubeRequest.setSuccessful(true);
+        return cubeRequest;
+    }
+
+    /**
+     * Get available table list of the input database
+     * 
+     * @return Table metadata array
+     * @throws JsonProcessingException 
+     * @throws IOException
+     */
+    @RequestMapping(value = "", method = { RequestMethod.PUT })
+    @ResponseBody
+    @Metered(name = "updateCube")
+    public CubeRequest updateCubeDesc(@RequestBody CubeRequest cubeRequest) throws JsonProcessingException {
+        CubeDesc desc = deserializeCubeDesc(cubeRequest);
+
+        if (desc == null) {
+            return cubeRequest;
+        }
+
+        // Check if the cube is editable
+        if (!cubeService.isCubeDescEditable(desc)) {
+            String error = "Cube desc " + desc.getName().toUpperCase() + " is not editable.";
+            updateRequest(cubeRequest, false, error);
+            return cubeRequest;
+        }
+
+        try {
+            CubeInstance cube = cubeService.getCubeManager().getCube(cubeRequest.getCubeName());
+            String projectName = (null == cubeRequest.getProject()) ? ProjectInstance.DEFAULT_PROJECT_NAME : cubeRequest.getProject();
+            desc = cubeService.updateCubeAndDesc(cube, desc, projectName);
+
+        } catch (AccessDeniedException accessDeniedException) {
+            throw new ForbiddenException("You don't have right to update this cube.");
+        } catch (Exception e) {
+            logger.error("Failed to deal with the request.", e);
+            throw new InternalErrorException("Failed to deal with the request: " + e.getMessage());
+        }
+
+        if (desc.getError().isEmpty()) {
+            cubeRequest.setSuccessful(true);
+        } else {
+            logger.warn("Cube " + desc.getName() + " fail to create because " + desc.getError());
+            updateRequest(cubeRequest, false, omitMessage(desc.getError()));
+        }
+        String descData = JsonUtil.writeValueAsIndentString(desc);
+        cubeRequest.setCubeDescData(descData);
+
+        return cubeRequest;
+    }
+
+    /**
+     * Get available table list of the input database
+     * 
+     * @return true
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{cubeName}/hbase", method = { RequestMethod.GET })
+    @ResponseBody
+    @Metered(name = "getHBaseInfo")
+    public List<HBaseResponse> getHBaseInfo(@PathVariable String cubeName) {
+        List<HBaseResponse> hbase = new ArrayList<HBaseResponse>();
+
+        CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+        if (null == cube) {
+            throw new InternalErrorException("Cannot find cube " + cubeName);
+        }
+
+        List<CubeSegment> segments = cube.getSegments();
+
+        for (CubeSegment segment : segments) {
+            String tableName = segment.getStorageLocationIdentifier();
+            HBaseResponse hr = null;
+
+            // Get info of given table.
+            try {
+                hr = cubeService.getHTableInfo(tableName);
+            } catch (IOException e) {
+                logger.error("Failed to calcuate size of HTable \"" + tableName + "\".", e);
+            }
+
+            if (null == hr) {
+                logger.info("Failed to calcuate size of HTable \"" + tableName + "\".");
+                hr = new HBaseResponse();
+            }
+
+            hr.setTableName(tableName);
+            hbase.add(hr);
+        }
+
+        return hbase;
+    }
+
+    private CubeDesc deserializeCubeDesc(CubeRequest cubeRequest) {
+        CubeDesc desc = null;
+        try {
+            logger.debug("Saving cube " + cubeRequest.getCubeDescData());
+            desc = JsonUtil.readValue(cubeRequest.getCubeDescData(), CubeDesc.class);
+        } catch (JsonParseException e) {
+            logger.error("The cube definition is not valid.", e);
+            updateRequest(cubeRequest, false, e.getMessage());
+        } catch (JsonMappingException e) {
+            logger.error("The cube definition is not valid.", e);
+            updateRequest(cubeRequest, false, e.getMessage());
+        } catch (IOException e) {
+            logger.error("Failed to deal with the request.", e);
+            throw new InternalErrorException("Failed to deal with the request:"+e.getMessage(), e);
+        }
+        return desc;
+    }
+
+    /**
+     * @param error
+     * @return
+     */
+    private String omitMessage(List<String> errors) {
+        StringBuffer buffer = new StringBuffer();
+        for (Iterator<String> iterator = errors.iterator(); iterator.hasNext();) {
+            String string = (String) iterator.next();
+            buffer.append(string);
+            buffer.append("\n");
+        }
+        return buffer.toString();
+    }
+
+    private void updateRequest(CubeRequest request, boolean success, String message) {
+        request.setCubeDescData("");
+        request.setSuccessful(success);
+        request.setMessage(message);
+    }
+
+    public void setCubeService(CubeService cubeService) {
+        this.cubeService = cubeService;
+    }
+
+    public void setJobService(JobService jobService) {
+        this.jobService = jobService;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/CubeDescController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/CubeDescController.java b/server/src/main/java/com/kylinolap/rest/controller/CubeDescController.java
new file mode 100644
index 0000000..173c3b6
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/CubeDescController.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.controller;
+
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.kylinolap.cube.CubeInstance;
+import com.kylinolap.metadata.model.cube.CubeDesc;
+import com.kylinolap.rest.service.CubeService;
+
+/**
+ * @author xduo
+ * 
+ */
+@Controller
+@RequestMapping(value = "/cube_desc")
+public class CubeDescController {
+
+    @Autowired
+    private CubeService cubeService;
+
+    /**
+     * Get detail information of the "Cube ID"
+     * 
+     * @param cubeDescName
+     *            Cube ID
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{cubeName}", method = { RequestMethod.GET })
+    @ResponseBody
+    public CubeDesc[] getCube(@PathVariable String cubeName) {
+        CubeInstance cubeInstance = cubeService.getCubeManager().getCube(cubeName);
+        CubeDesc cSchema = cubeInstance.getDescriptor();
+        if (cSchema != null) {
+            return new CubeDesc[] { cSchema };
+        } else {
+            return null;
+        }
+    }
+
+    public void setCubeService(CubeService cubeService) {
+        this.cubeService = cubeService;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/JobController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/JobController.java b/server/src/main/java/com/kylinolap/rest/controller/JobController.java
new file mode 100644
index 0000000..5f26ad9
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/JobController.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.controller;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.kylinolap.common.KylinConfig;
+import com.kylinolap.job.JobInstance;
+import com.kylinolap.job.JobManager;
+import com.kylinolap.job.constant.JobStatusEnum;
+import com.kylinolap.rest.constant.Constant;
+import com.kylinolap.rest.exception.InternalErrorException;
+import com.kylinolap.rest.request.JobListRequest;
+import com.kylinolap.rest.service.JobService;
+
+/**
+ * @author ysong1
+ * @author Jack
+ * 
+ */
+@Controller
+@RequestMapping(value = "jobs")
+public class JobController extends BasicController implements InitializingBean {
+    private static final Logger logger = LoggerFactory.getLogger(JobController.class);
+
+    @Autowired
+    private JobService jobService;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+     */
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        String timeZone = jobService.getKylinConfig().getTimeZone();
+        TimeZone tzone = TimeZone.getTimeZone(timeZone);
+        TimeZone.setDefault(tzone);
+
+        String serverMode = KylinConfig.getInstanceFromEnv().getServerMode();
+
+        if (Constant.SERVER_MODE_JOB.equals(serverMode.toLowerCase()) || Constant.SERVER_MODE_ALL.equals(serverMode.toLowerCase())) {
+            logger.info("Initializing Job Engine ....");
+
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    JobManager jobManager = null;
+                    try {
+                        jobManager = jobService.getJobManager();
+                        jobManager.startJobEngine();
+                        metricsService.registerJobMetrics(jobManager);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }).start();
+        }
+    }
+
+    /**
+     * get all cube jobs
+     * 
+     * @param cubeName
+     *            Cube ID
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value = "", method = { RequestMethod.GET })
+    @ResponseBody
+    public List<JobInstance> list(JobListRequest jobRequest) {
+
+        List<JobInstance> jobInstanceList = Collections.emptyList();
+        List<JobStatusEnum> statusList = new ArrayList<JobStatusEnum>();
+
+        if (null != jobRequest.getStatus()) {
+            for (int status : jobRequest.getStatus()) {
+                statusList.add(JobStatusEnum.getByCode(status));
+            }
+        }
+
+        try {
+            jobInstanceList = jobService.listAllJobs(jobRequest.getCubeName(), jobRequest.getProjectName(), statusList, jobRequest.getLimit(), jobRequest.getOffset());
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e);
+        }
+        return jobInstanceList;
+    }
+
+    /**
+     * Get a cube job
+     * 
+     * @param cubeName
+     *            Cube ID
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{jobId}", method = { RequestMethod.GET })
+    @ResponseBody
+    public JobInstance get(@PathVariable String jobId) {
+        JobInstance jobInstance = null;
+        try {
+            jobInstance = jobService.getJobInstance(jobId);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e);
+        }
+
+        return jobInstance;
+    }
+
+    /**
+     * Get a job step output
+     * 
+     * @param cubeName
+     *            Cube ID
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{jobId}/steps/{stepId}/output", method = { RequestMethod.GET })
+    @ResponseBody
+    public Map<String, String> getStepOutput(@PathVariable String jobId, @PathVariable int stepId) {
+        Map<String, String> result = new HashMap<String, String>();
+        result.put("jobId", jobId);
+        result.put("stepId", String.valueOf(stepId));
+        long start = System.currentTimeMillis();
+        String output = "";
+
+        try {
+            output = jobService.getJobManager().getJobStepOutput(jobId, stepId);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e);
+        }
+
+        result.put("cmd_output", output);
+        long end = System.currentTimeMillis();
+        logger.info("Complete fetching step " + jobId + ":" + stepId + " output in " + (end - start) + " seconds");
+        return result;
+    }
+
+    /**
+     * Resume a cube job
+     * 
+     * @param String
+     *            Job ID
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{jobId}/resume", method = { RequestMethod.PUT })
+    @ResponseBody
+    public JobInstance resume(@PathVariable String jobId) {
+        JobInstance jobInstance = null;
+        try {
+            jobInstance = jobService.getJobInstance(jobId);
+            jobService.resumeJob(jobInstance);
+            jobInstance = jobService.getJobInstance(jobId);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e);
+        }
+
+        return jobInstance;
+    }
+
+    /**
+     * Cancel a job
+     * 
+     * @param String
+     *            Job ID
+     * @return
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{jobId}/cancel", method = { RequestMethod.PUT })
+    @ResponseBody
+    public JobInstance cancel(@PathVariable String jobId) {
+
+        JobInstance jobInstance = null;
+        try {
+            jobInstance = jobService.getJobInstance(jobId);
+            jobService.cancelJob(jobInstance);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e);
+        }
+
+        return jobInstance;
+    }
+
+    public void setJobService(JobService jobService) {
+        this.jobService = jobService;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/ProjectController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/ProjectController.java b/server/src/main/java/com/kylinolap/rest/controller/ProjectController.java
new file mode 100644
index 0000000..3b15dc9
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/ProjectController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.controller;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.codahale.metrics.annotation.Metered;
+import com.kylinolap.cube.project.ProjectInstance;
+import com.kylinolap.rest.exception.InternalErrorException;
+import com.kylinolap.rest.request.CreateProjectRequest;
+import com.kylinolap.rest.request.UpdateProjectRequest;
+import com.kylinolap.rest.service.ProjectService;
+
+/**
+ * @author xduo
+ */
+@Controller
+@RequestMapping(value = "/projects")
+public class ProjectController extends BasicController {
+    private static final Logger logger = LoggerFactory.getLogger(ProjectController.class);
+
+    @Autowired
+    private ProjectService projectService;
+
+    /**
+     * Get available project list
+     * 
+     * @return Table metadata array
+     * @throws IOException
+     */
+    @RequestMapping(value = "", method = { RequestMethod.GET })
+    @ResponseBody
+    public List<ProjectInstance> getProjects(@RequestParam(value = "limit", required = false) Integer limit, @RequestParam(value = "offset", required = false) Integer offset) {
+        return projectService.listAllProjects(limit, offset);
+    }
+
+    @RequestMapping(value = "", method = { RequestMethod.POST })
+    @ResponseBody
+    @Metered(name = "saveProject")
+    public ProjectInstance saveProject(@RequestBody CreateProjectRequest projectRequest) {
+        if (StringUtils.isEmpty(projectRequest.getName())) {
+            throw new InternalErrorException("A project name must be given to create a project");
+        }
+
+        ProjectInstance createdProj = null;
+        try {
+            createdProj = projectService.createProject(projectRequest);
+        } catch (Exception e) {
+            logger.error("Failed to deal with the request.", e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+
+        return createdProj;
+    }
+
+    @RequestMapping(value = "", method = { RequestMethod.PUT })
+    @ResponseBody
+    @Metered(name = "updateProject")
+    public ProjectInstance updateProject(@RequestBody UpdateProjectRequest projectRequest) {
+        if (StringUtils.isEmpty(projectRequest.getFormerProjectName())) {
+            throw new InternalErrorException("A project name must be given to update a project");
+        }
+
+        ProjectInstance updatedProj = null;
+        try {
+            updatedProj = projectService.updateProject(projectRequest);
+        } catch (Exception e) {
+            logger.error("Failed to deal with the request.", e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+
+        return updatedProj;
+    }
+
+    @RequestMapping(value = "/{projectName}", method = { RequestMethod.DELETE })
+    @ResponseBody
+    @Metered(name = "deleteProject")
+    public void deleteProject(@PathVariable String projectName) {
+        try {
+            projectService.deleteProject(projectName);
+        } catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException("Failed to delete project. " + " Caused by: " + e.getMessage(), e);
+        }
+    }
+
+    public void setProjectService(ProjectService projectService) {
+        this.projectService = projectService;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/QueryController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/QueryController.java b/server/src/main/java/com/kylinolap/rest/controller/QueryController.java
new file mode 100644
index 0000000..e080aff
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/QueryController.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the <dependency>
+    <groupId>com.ryantenney.metrics</groupId>
+    <artifactId>metrics-spring</artifactId>
+    <version>3.0.0</version>
+</dependency>che License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kylinolap.rest.controller;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.supercsv.io.CsvListWriter;
+import org.supercsv.io.ICsvListWriter;
+import org.supercsv.prefs.CsvPreference;
+
+import com.codahale.metrics.annotation.Timed;
+import com.kylinolap.common.KylinConfig;
+import com.kylinolap.cube.CubeInstance;
+import com.kylinolap.rest.constant.Constant;
+import com.kylinolap.rest.exception.ForbiddenException;
+import com.kylinolap.rest.exception.InternalErrorException;
+import com.kylinolap.rest.model.Query;
+import com.kylinolap.rest.model.SelectedColumnMeta;
+import com.kylinolap.rest.model.TableMeta;
+import com.kylinolap.rest.request.MetaRequest;
+import com.kylinolap.rest.request.PrepareSqlRequest;
+import com.kylinolap.rest.request.SQLRequest;
+import com.kylinolap.rest.request.SaveSqlRequest;
+import com.kylinolap.rest.response.SQLResponse;
+import com.kylinolap.rest.service.QueryService;
+import com.kylinolap.rest.util.QueryUtil;
+
+/**
+ * Handle query requests.
+ * 
+ * @author xduo
+ */
+@Controller
+public class QueryController extends BasicController {
+
+    private static final Logger logger = LoggerFactory.getLogger(QueryController.class);
+
+    public static final String SUCCESS_QUERY_CACHE = "SuccessQueryCache";
+    public static final String EXCEPTION_QUERY_CACHE = "ExceptionQueryCache";
+
+    @Autowired
+    private QueryService queryService;
+
+    @Autowired
+    private CacheManager cacheManager;
+
+    @RequestMapping(value = "/query", method = RequestMethod.POST)
+    @ResponseBody
+    @Timed(name = "query")
+    public SQLResponse query(@RequestBody SQLRequest sqlRequest) {
+        long startTimestamp = System.currentTimeMillis();
+
+        SQLResponse response = doQuery(sqlRequest);
+        response.setDuration(System.currentTimeMillis() - startTimestamp);
+
+        queryService.logQuery(sqlRequest, response, new Date(startTimestamp), new Date(System.currentTimeMillis()));
+
+        return response;
+    }
+
+    @RequestMapping(value = "/query/prestate", method = RequestMethod.POST, produces = "application/json")
+    @ResponseBody
+    @Timed(name = "query")
+    public SQLResponse prepareQuery(@RequestBody PrepareSqlRequest sqlRequest) {
+        long startTimestamp = System.currentTimeMillis();
+
+        SQLResponse response = doQuery(sqlRequest);
+        response.setDuration(System.currentTimeMillis() - startTimestamp);
+
+        queryService.logQuery(sqlRequest, response, new Date(startTimestamp), new Date(System.currentTimeMillis()));
+
+        if (response.getIsException()) {
+            String errorMsg = response.getExceptionMessage();
+            throw new InternalErrorException(QueryUtil.makeErrorMsgUserFriendly(errorMsg));
+        }
+
+        return response;
+    }
+
+    @RequestMapping(value = "/saved_queries", method = RequestMethod.POST)
+    @ResponseBody
+    @Timed(name = "saveQuery")
+    public void saveQuery(@RequestBody SaveSqlRequest sqlRequest) throws IOException {
+        String creator = SecurityContextHolder.getContext().getAuthentication().getName();
+        Query newQuery = new Query(sqlRequest.getName(), sqlRequest.getProject(), sqlRequest.getSql(), sqlRequest.getDescription());
+
+        queryService.saveQuery(creator, newQuery);
+    }
+
+    @RequestMapping(value = "/saved_queries/{id}", method = RequestMethod.DELETE)
+    @ResponseBody
+    @Timed(name = "removeQuery")
+    public void removeQuery(@PathVariable String id) throws IOException {
+        String creator = SecurityContextHolder.getContext().getAuthentication().getName();
+        queryService.removeQuery(creator, id);
+    }
+
+    @RequestMapping(value = "/saved_queries", method = RequestMethod.GET)
+    @ResponseBody
+    @Timed(name = "getQueries")
+    public List<Query> getQueries() throws IOException {
+        String creator = SecurityContextHolder.getContext().getAuthentication().getName();
+        return queryService.getQueries(creator);
+    }
+
+    @RequestMapping(value = "/query/format/{format}", method = RequestMethod.GET)
+    @ResponseBody
+    @Timed(name = "downloadResult")
+    public void downloadQueryResult(@PathVariable String format, SQLRequest sqlRequest, HttpServletResponse response) {
+        SQLResponse result = doQuery(sqlRequest);
+        response.setContentType("text/" + format + ";charset=utf-8");
+        response.setHeader("Content-Disposition", "attachment; filename=\"result." + format + "\"");
+        ICsvListWriter csvWriter = null;
+
+        try {
+            csvWriter = new CsvListWriter(response.getWriter(), CsvPreference.STANDARD_PREFERENCE);
+
+            List<String> headerList = new ArrayList<String>();
+
+            for (SelectedColumnMeta column : result.getColumnMetas()) {
+                headerList.add(column.getName());
+            }
+
+            String[] headers = new String[headerList.size()];
+            csvWriter.writeHeader(headerList.toArray(headers));
+
+            for (List<String> row : result.getResults()) {
+                csvWriter.write(row);
+            }
+        } catch (IOException e) {
+            logger.error("", e);
+        } finally {
+            IOUtils.closeQuietly(csvWriter);
+        }
+    }
+
+    @RequestMapping(value = "/tables_and_columns", method = RequestMethod.GET)
+    @ResponseBody
+    public List<TableMeta> getMetadata(MetaRequest metaRequest) {
+        try {
+            return queryService.getMetadata(metaRequest.getProject());
+        } catch (SQLException e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new InternalErrorException(e.getLocalizedMessage(), e);
+        }
+    }
+
+    private SQLResponse doQuery(SQLRequest sqlRequest) {
+        String sql = sqlRequest.getSql();
+        String project = sqlRequest.getProject();
+        logger.info("Using project: " + project);
+        logger.info("The original query:  " + sql);
+
+        String serverMode = KylinConfig.getInstanceFromEnv().getServerMode();
+        if (!(Constant.SERVER_MODE_QUERY.equals(serverMode.toLowerCase()) || Constant.SERVER_MODE_ALL.equals(serverMode.toLowerCase()))) {
+            throw new InternalErrorException("Query is not allowed in " + serverMode + " mode.");
+        }
+
+        if (sql.toLowerCase().contains("select")) {
+            SQLResponse sqlResponse = searchQueryInCache(sqlRequest);
+            try {
+                if (null == sqlResponse) {
+                    sqlResponse = queryService.query(sqlRequest);
+
+                    long durationThreshold = KylinConfig.getInstanceFromEnv().getQueryDurationCacheThreshold();
+                    long scancountThreshold = KylinConfig.getInstanceFromEnv().getQueryScanCountCacheThreshold();
+                    if (!sqlResponse.getIsException() && (sqlResponse.getDuration() > durationThreshold || sqlResponse.getTotalScanCount() > scancountThreshold)) {
+                        cacheManager.getCache(SUCCESS_QUERY_CACHE).put(new Element(sqlRequest, sqlResponse));
+                    }
+                }
+
+                checkQueryAuth(sqlResponse);
+
+                return sqlResponse;
+            } catch (AccessDeniedException ade) {
+                // Access exception is bind with each user, it will not be
+                // cached.
+                logger.error("Exception when execute sql", ade);
+                throw new ForbiddenException(ade.getLocalizedMessage());
+            } catch (Exception e) {
+                SQLResponse exceptionRes = new SQLResponse(null, null, 0, true, e.getMessage());
+                Cache exceptionCache = cacheManager.getCache(EXCEPTION_QUERY_CACHE);
+                exceptionCache.put(new Element(sqlRequest, exceptionRes));
+
+                logger.error("Exception when execute sql", e);
+                throw new InternalErrorException(QueryUtil.makeErrorMsgUserFriendly(e.getLocalizedMessage()));
+            }
+        } else {
+            logger.debug("Directly return expection as not supported");
+            throw new InternalErrorException(QueryUtil.makeErrorMsgUserFriendly("Not Supported SQL."));
+        }
+    }
+
+    private SQLResponse searchQueryInCache(SQLRequest sqlRequest) {
+        SQLResponse response = null;
+        Cache exceptionCache = cacheManager.getCache(EXCEPTION_QUERY_CACHE);
+        Cache queryCache = cacheManager.getCache(SUCCESS_QUERY_CACHE);
+
+        if (KylinConfig.getInstanceFromEnv().isQueryCacheEnabled() && null != exceptionCache.get(sqlRequest)) {
+            Element element = exceptionCache.get(sqlRequest);
+            response = (SQLResponse) element.getObjectValue();
+            response.setHitCache(true);
+        } else if (KylinConfig.getInstanceFromEnv().isQueryCacheEnabled() && null != queryCache.get(sqlRequest)) {
+            Element element = queryCache.get(sqlRequest);
+            response = (SQLResponse) element.getObjectValue();
+            response.setHitCache(true);
+        }
+
+        return response;
+    }
+
+    private void checkQueryAuth(SQLResponse sqlResponse) throws AccessDeniedException {
+        if (!sqlResponse.getIsException() && KylinConfig.getInstanceFromEnv().isQuerySecureEnabled()) {
+            CubeInstance cubeInstance = this.queryService.getCubeManager().getCube(sqlResponse.getCube());
+            queryService.checkAuthorization(cubeInstance);
+        }
+    }
+
+    public void setQueryService(QueryService queryService) {
+        this.queryService = queryService;
+    }
+
+    public void setCacheManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/TableController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/TableController.java b/server/src/main/java/com/kylinolap/rest/controller/TableController.java
new file mode 100644
index 0000000..0ffdfe5
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/TableController.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.controller;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.codahale.metrics.annotation.Metered;
+import com.kylinolap.metadata.MetadataConstances;
+import com.kylinolap.metadata.model.schema.ColumnDesc;
+import com.kylinolap.metadata.model.schema.TableDesc;
+import com.kylinolap.rest.exception.InternalErrorException;
+import com.kylinolap.rest.request.CardinalityRequest;
+import com.kylinolap.rest.response.TableDescResponse;
+import com.kylinolap.rest.service.CubeService;
+
+/**
+ * @author xduo
+ * 
+ */
+@Controller
+@RequestMapping(value = "/tables")
+public class TableController extends BasicController {
+    private static final Logger logger = LoggerFactory.getLogger(TableController.class);
+
+    @Autowired
+    private CubeService cubeMgmtService;
+
+    /**
+     * Get available table list of the input database
+     * 
+     * @return Table metadata array
+     * @throws IOException
+     */
+    @RequestMapping(value = "", method = { RequestMethod.GET })
+    @ResponseBody
+    @Metered(name = "listSourceTables")
+    public List<TableDesc> getHiveTables(@RequestParam(value = "ext", required = false) boolean withExt,@RequestParam(value = "project", required = false) String project ) {
+        long start = System.currentTimeMillis();
+        List<TableDesc> tables = null;
+        try {
+                tables = cubeMgmtService.getProjectManager().listDefinedTablesInProject(project);
+        } catch (IOException e) {
+            logger.error("Failed to deal with the request.", e);
+            throw new InternalErrorException(e.getLocalizedMessage());
+        }
+
+        if (withExt) {
+            tables = cloneTableDesc(tables);
+        }
+        long end = System.currentTimeMillis();
+        logger.info("Return all table metadata in " + (end - start) + " seconds");
+
+        return tables;
+    }
+
+    /**
+     * Get available table list of the input database
+     * 
+     * @return Table metadata array
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{tableName}", method = { RequestMethod.GET })
+    @ResponseBody
+    public TableDesc getHiveTable(@PathVariable String tableName) {
+        return cubeMgmtService.getMetadataManager().getTableDesc(tableName);
+    }
+
+    /**
+     * Get available table list of the input database
+     * 
+     * @return Table metadata array
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{tableName}/exd-map", method = { RequestMethod.GET })
+    @ResponseBody
+    public Map<String, String> getHiveTableExd(@PathVariable String tableName) {
+        Map<String, String> tableExd = cubeMgmtService.getMetadataManager().getTableDescExd(tableName);
+        return tableExd;
+    }
+
+    @RequestMapping(value = "/reload", method = { RequestMethod.PUT })
+    @ResponseBody
+    public String reloadSourceTable() {
+        cubeMgmtService.getMetadataManager().reload();
+        return "ok";
+    }
+
+    @RequestMapping(value = "/{tables}/{project}", method = { RequestMethod.POST })
+    @ResponseBody
+    public Map<String, String[]> loadHiveTable(@PathVariable String tables,@PathVariable String project){
+        Map<String, String[]> result = new HashMap<String, String[]>();
+        try{ 
+            String[] loadedTables = cubeMgmtService.reloadHiveTable(tables);
+
+            String inputTables[] = tables.split(",");
+            ArrayList<String> unloadedTables = new ArrayList<String>();
+            for (String inputTable : inputTables) {
+                boolean tableLoaded = false;
+                for (String loadedTable : loadedTables) {
+                    int cut = loadedTable.indexOf('.');
+                    String tableName = cut >= 0 ? loadedTable.substring(cut + 1).trim() : loadedTable.trim();
+                    if (inputTable.trim().toUpperCase().equals(tableName)||inputTable.trim().toUpperCase().equals(loadedTable)) {
+                        tableLoaded = true;
+                        break;
+                    }
+                }
+                if(!tableLoaded){
+                    unloadedTables.add(inputTable);
+                }
+            }
+           
+            cubeMgmtService.syncTableToProject(loadedTables, project);
+            result.put("result.loaded", loadedTables);
+            result.put("result.unloaded",unloadedTables.toArray(new String[unloadedTables.size()]) );
+            
+       }catch(IOException e){
+           logger.error("Failed to deal with the request.", e);
+           throw new InternalErrorException("Failed to load table,Please check the table name.");
+       }
+        return result;
+    }
+
+    /**
+     * Regenerate table cardinality
+     * 
+     * @return Table metadata array
+     * @throws IOException
+     */
+    @RequestMapping(value = "/{tableNames}/cardinality", method = { RequestMethod.PUT })
+    @ResponseBody
+    public CardinalityRequest generateCardinality(@PathVariable String tableNames, @RequestBody CardinalityRequest request) {
+        String[] tables = tableNames.split(",");
+        for (String table : tables) {
+            cubeMgmtService.generateCardinality(table.trim(), request.getFormat(), request.getDelimiter());
+        }
+        return request;
+    }
+
+    /**
+     * @param tables
+     * @return
+     */
+    private List<TableDesc> cloneTableDesc(List<TableDesc> tables) {
+        if (null == tables) {
+            return Collections.emptyList();
+        }
+
+        List<TableDesc> descs = new ArrayList<TableDesc>();
+        Iterator<TableDesc> it = tables.iterator();
+        while (it.hasNext()) {
+            TableDesc table = it.next();
+            Map<String, String> exd = cubeMgmtService.getMetadataManager().getTableDescExd(table.getName());
+            if (exd == null) {
+                descs.add(table);
+            } else {
+                // Clone TableDesc
+                TableDescResponse rtableDesc = new TableDescResponse(table);
+                rtableDesc.setDescExd(exd);
+                if (exd.containsKey(MetadataConstances.TABLE_EXD_CARDINALITY)) {
+                    Map<String, Long> cardinality = new HashMap<String, Long>();
+                    String scard = exd.get(MetadataConstances.TABLE_EXD_CARDINALITY);
+                    if (!StringUtils.isEmpty(scard)) {
+                        String[] cards = StringUtils.split(scard, ",");
+                        ColumnDesc[] cdescs = rtableDesc.getColumns();
+                        for (int i = 0; i < cdescs.length; i++) {
+                            ColumnDesc columnDesc = cdescs[i];
+                            if (cards.length > i) {
+                                cardinality.put(columnDesc.getName(), Long.parseLong(cards[i]));
+                            } else {
+                                logger.error("The result cardinality is not identical with hive table metadata, cardinaly : " + scard + " column array length: " + cdescs.length);
+                                break;
+                            }
+                        }
+                        rtableDesc.setCardinality(cardinality);
+                    }
+                }
+                descs.add(rtableDesc);
+            }
+        }
+        return descs;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/controller/UserController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/controller/UserController.java b/server/src/main/java/com/kylinolap/rest/controller/UserController.java
new file mode 100644
index 0000000..25b6bb2
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/controller/UserController.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.controller;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import com.kylinolap.rest.service.UserService;
+
+/**
+ * Handle user authentication request to protected kylin rest resources by
+ * spring security.
+ * 
+ * @author xduo
+ * 
+ */
+@Controller
+@RequestMapping(value = "/user")
+public class UserController {
+
+    @Autowired
+    UserService userService;
+
+    @RequestMapping(value = "/authentication", method = RequestMethod.POST, produces = "application/json")
+    public UserDetails authenticate() {
+        return authenticatedUser();
+    }
+
+    @RequestMapping(value = "/authentication", method = RequestMethod.GET, produces = "application/json")
+    public UserDetails authenticatedUser() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        if (authentication == null || !(authentication.getPrincipal() instanceof UserDetails)) {
+            return null;
+        }
+
+        return (UserDetails) authentication.getPrincipal();
+    }
+
+    @RequestMapping(value = "/authentication/authorities", method = RequestMethod.GET, produces = "application/json")
+    public List<String> getAuthorities() {
+        return userService.getUserAuthorities();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/exception/BadRequestException.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/exception/BadRequestException.java b/server/src/main/java/com/kylinolap/rest/exception/BadRequestException.java
new file mode 100644
index 0000000..0bf31c9
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/exception/BadRequestException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kylinolap.rest.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+
+/**
+ * @author xduo
+ *
+ */
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class BadRequestException extends RuntimeException {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -6798154278095441848L;
+
+    public BadRequestException(String s) {
+        super(s);
+    }
+
+    /**
+     * 
+     */
+    public BadRequestException() {
+        super();
+    }
+
+    /**
+     * @param arg0
+     * @param arg1
+     */
+    public BadRequestException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    /**
+     * @param arg0
+     */
+    public BadRequestException(Throwable arg0) {
+        super(arg0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/exception/ForbiddenException.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/exception/ForbiddenException.java b/server/src/main/java/com/kylinolap/rest/exception/ForbiddenException.java
new file mode 100644
index 0000000..25e53a3
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/exception/ForbiddenException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * @author xduo
+ * 
+ */
+@ResponseStatus(value = HttpStatus.FORBIDDEN)
+public class ForbiddenException extends RuntimeException {
+
+    private static final long serialVersionUID = 2741885728370162194L;
+
+    public ForbiddenException() {
+        super();
+    }
+
+    public ForbiddenException(String message) {
+        super(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/exception/InternalErrorException.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/exception/InternalErrorException.java b/server/src/main/java/com/kylinolap/rest/exception/InternalErrorException.java
new file mode 100644
index 0000000..4198fda
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/exception/InternalErrorException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kylinolap.rest.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * Class to wrap backend exception
+ * 
+ * @author jianliu
+ * 
+ */
+@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
+public class InternalErrorException extends RuntimeException {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -6798154278095441848L;
+
+    public InternalErrorException(String s) {
+        super(s);
+    }
+
+    /**
+     * 
+     */
+    public InternalErrorException() {
+        super();
+    }
+
+    /**
+     * @param arg0
+     * @param arg1
+     */
+    public InternalErrorException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    /**
+     * @param arg0
+     */
+    public InternalErrorException(Throwable arg0) {
+        super(arg0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/exception/NotFoundException.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/exception/NotFoundException.java b/server/src/main/java/com/kylinolap/rest/exception/NotFoundException.java
new file mode 100644
index 0000000..ede21bf
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/exception/NotFoundException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kylinolap.rest.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+ 
+/**
+ * @author xduo
+ *
+ */
+@ResponseStatus(value = HttpStatus.NOT_FOUND)
+public class NotFoundException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    public NotFoundException(String s) {
+        super(s);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/interceptor/CacheIntercaptor.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/interceptor/CacheIntercaptor.java b/server/src/main/java/com/kylinolap/rest/interceptor/CacheIntercaptor.java
new file mode 100644
index 0000000..2b763dd
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/interceptor/CacheIntercaptor.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.interceptor;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+import com.kylinolap.common.restclient.Broadcaster;
+
+/**
+ * @author xduo
+ * 
+ */
+@Aspect
+@Component("cacheIntercaptor")
+public class CacheIntercaptor {
+
+    @After("execution(public * com.kylinolap.rest.controller.CubeController.*(..)) || execution(public * com.kylinolap.rest.controller.ProjectController.*(..))")
+    public void flush(JoinPoint joinPoint) {
+        String methodName = joinPoint.getSignature().getName();
+
+        if (methodName.matches("(update|create|save|disable|enable|delete|drop|purge)")) {
+            Broadcaster.flush();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/metrics/JobMetrics.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/metrics/JobMetrics.java b/server/src/main/java/com/kylinolap/rest/metrics/JobMetrics.java
new file mode 100644
index 0000000..4fde585
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/metrics/JobMetrics.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.metrics;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricSet;
+import com.kylinolap.job.JobManager;
+
+/**
+ * @author xduo
+ * 
+ */
+public class JobMetrics implements MetricSet {
+
+    private JobManager jobManager;
+
+    static class JobMetricsHolder {
+        static final JobMetrics INSTANCE = new JobMetrics();
+    }
+
+    public static JobMetrics getInstance() {
+        return JobMetricsHolder.INSTANCE;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.codahale.metrics.MetricSet#getMetrics()
+     */
+    @Override
+    public Map<String, Metric> getMetrics() {
+        Map<String, Metric> metricSet = new HashMap<String, Metric>();
+
+        metricSet.put("PercentileJobStepDuration", new Gauge<Double>() {
+            @Override
+            public Double getValue() {
+                return jobManager.getPercentileJobStepDuration(95);
+            }
+        });
+
+        metricSet.put("scheduledJobs", new Gauge<Integer>() {
+            @Override
+            public Integer getValue() {
+                return jobManager.getScheduledJobsSzie();
+            }
+        });
+
+        metricSet.put("EngineThreadPool", new Gauge<Integer>() {
+            @Override
+            public Integer getValue() {
+                return jobManager.getEngineThreadPoolSize();
+            }
+        });
+
+        metricSet.put("MaxJobStep", new Gauge<Double>() {
+            @Override
+            public Double getValue() {
+                return jobManager.getMaxJobStepDuration();
+            }
+        });
+
+        metricSet.put("MinJobStep", new Gauge<Double>() {
+            @Override
+            public Double getValue() {
+                return jobManager.getMinJobStepDuration();
+            }
+        });
+
+        metricSet.put("IdleSlots", new Gauge<Integer>() {
+            @Override
+            public Integer getValue() {
+                return jobManager.getNumberOfIdleSlots();
+            }
+        });
+
+        metricSet.put("JobStepsExecuted", new Gauge<Integer>() {
+            @Override
+            public Integer getValue() {
+                return jobManager.getNumberOfJobStepsExecuted();
+            }
+        });
+
+        metricSet.put("JobStepsRunning", new Gauge<Integer>() {
+            @Override
+            public Integer getValue() {
+                return jobManager.getNumberOfJobStepsRunning();
+            }
+        });
+
+        return metricSet;
+    }
+
+    public void setJobManager(JobManager jobManager) {
+        this.jobManager = jobManager;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/metrics/KylinInstrumentedFilterContextListener.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/metrics/KylinInstrumentedFilterContextListener.java b/server/src/main/java/com/kylinolap/rest/metrics/KylinInstrumentedFilterContextListener.java
new file mode 100644
index 0000000..e54db3e
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/metrics/KylinInstrumentedFilterContextListener.java
@@ -0,0 +1,39 @@
+package com.kylinolap.rest.metrics;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.servlet.InstrumentedFilterContextListener;
+
+public class KylinInstrumentedFilterContextListener implements ServletContextListener {
+
+    @Autowired
+    private MetricRegistry metricRegistry;
+
+    private final InnerKylinInstrumentedFilterContextListener innerKylinInstrumentedFilterContextListener = new InnerKylinInstrumentedFilterContextListener();
+
+    @Override
+    public void contextInitialized(ServletContextEvent event) {
+        WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()).getAutowireCapableBeanFactory().autowireBean(this);
+
+        innerKylinInstrumentedFilterContextListener.contextInitialized(event);
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent event) {
+    }
+
+    class InnerKylinInstrumentedFilterContextListener extends InstrumentedFilterContextListener {
+
+        @Override
+        protected MetricRegistry getMetricRegistry() {
+            return metricRegistry;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/metrics/QueryMetrics.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/metrics/QueryMetrics.java b/server/src/main/java/com/kylinolap/rest/metrics/QueryMetrics.java
new file mode 100644
index 0000000..0748314
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/metrics/QueryMetrics.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kylinolap.rest.metrics;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricSet;
+
+/**
+ * @author xduo
+ * 
+ */
+public class QueryMetrics implements MetricSet {
+
+    private Map<String, Float> metrics = new HashMap<String, Float>();
+
+    private QueryMetrics() {
+        // register query metrics
+        this.increase("duration", (float) 0);
+        this.increase("totalScanCount", (float) 0);
+        this.increase("count", (float) 0);
+    }
+
+    static class QueryMetricsHolder {
+        static final QueryMetrics INSTANCE = new QueryMetrics();
+    }
+
+    public static QueryMetrics getInstance() {
+        return QueryMetricsHolder.INSTANCE;
+    }
+
+    public synchronized void increase(String key, Float value) {
+        if (metrics.containsKey(key)) {
+            metrics.put(key, metrics.get(key) + value);
+        } else {
+            metrics.put(key, value);
+        }
+    }
+
+    public synchronized Float getAndReset(String key) {
+        float value = metrics.get(key);
+        metrics.put(key, (float) 0);
+
+        return value;
+    }
+
+    public synchronized Map<String, Metric> getMetrics() {
+        Map<String, Metric> metricSet = new HashMap<String, Metric>();
+
+        for (final String key : metrics.keySet()) {
+            metricSet.put(key, new Gauge<Float>() {
+                @Override
+                public Float getValue() {
+                    float value = getAndReset(key);
+
+                    return value;
+                }
+            });
+        }
+
+        return metricSet;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/model/ColumnMeta.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/model/ColumnMeta.java b/server/src/main/java/com/kylinolap/rest/model/ColumnMeta.java
new file mode 100644
index 0000000..731684a
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/model/ColumnMeta.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2013-2014 eBay Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kylinolap.rest.model;
+
+import java.io.Serializable;
+
+/**
+ * Created by lukhan on 2/27/14.
+ */
+public class ColumnMeta implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private String TABLE_CAT;
+    private String TABLE_SCHEM;
+    private String TABLE_NAME;
+    private String COLUMN_NAME;
+    private int DATA_TYPE;
+    private String TYPE_NAME;
+    private int COLUMN_SIZE;
+    private int BUFFER_LENGTH;
+    private int DECIMAL_DIGITS;
+    private int NUM_PREC_RADIX;
+    private int NULLABLE;
+    private String REMARKS;
+    private String COLUMN_DEF;
+    private int SQL_DATA_TYPE;
+    private int SQL_DATETIME_SUB;
+    private int CHAR_OCTET_LENGTH;
+    private int ORDINAL_POSITION;
+    private String IS_NULLABLE;
+    private String SCOPE_CATLOG;
+    private String SCOPE_SCHEMA;
+    private String SCOPE_TABLE;
+    private short SOURCE_DATA_TYPE;
+    private String IS_AUTOINCREMENT;
+
+    public ColumnMeta() {
+    }
+
+    public ColumnMeta(String tABLE_CAT, String tABLE_SCHEM, String tABLE_NAME, String cOLUMN_NAME, int dATA_TYPE, String tYPE_NAME, int cOLUMN_SIZE, int bUFFER_LENGTH, int dECIMAL_DIGITS, int nUM_PREC_RADIX, int nULLABLE, String rEMARKS, String cOLUMN_DEF, int sQL_DATA_TYPE, int sQL_DATETIME_SUB, int cHAR_OCTET_LENGTH, int oRDINAL_POSITION, String iS_NULLABLE, String sCOPE_CATLOG, String sCOPE_SCHEMA, String sCOPE_TABLE, short sOURCE_DATA_TYPE, String iS_AUTOINCREMENT) {
+        super();
+        TABLE_CAT = tABLE_CAT;
+        TABLE_SCHEM = tABLE_SCHEM;
+        TABLE_NAME = tABLE_NAME;
+        COLUMN_NAME = cOLUMN_NAME;
+        DATA_TYPE = dATA_TYPE;
+        TYPE_NAME = tYPE_NAME;
+        COLUMN_SIZE = cOLUMN_SIZE;
+        BUFFER_LENGTH = bUFFER_LENGTH;
+        DECIMAL_DIGITS = dECIMAL_DIGITS;
+        NUM_PREC_RADIX = nUM_PREC_RADIX;
+        NULLABLE = nULLABLE;
+        REMARKS = rEMARKS;
+        COLUMN_DEF = cOLUMN_DEF;
+        SQL_DATA_TYPE = sQL_DATA_TYPE;
+        SQL_DATETIME_SUB = sQL_DATETIME_SUB;
+        CHAR_OCTET_LENGTH = cHAR_OCTET_LENGTH;
+        ORDINAL_POSITION = oRDINAL_POSITION;
+        IS_NULLABLE = iS_NULLABLE;
+        SCOPE_CATLOG = sCOPE_CATLOG;
+        SCOPE_SCHEMA = sCOPE_SCHEMA;
+        SCOPE_TABLE = sCOPE_TABLE;
+        SOURCE_DATA_TYPE = sOURCE_DATA_TYPE;
+        IS_AUTOINCREMENT = iS_AUTOINCREMENT;
+    }
+
+    public String getTABLE_CAT() {
+        return TABLE_CAT;
+    }
+
+    public void setTABLE_CAT(String tABLE_CAT) {
+        TABLE_CAT = tABLE_CAT;
+    }
+
+    public String getTABLE_SCHEM() {
+        return TABLE_SCHEM;
+    }
+
+    public void setTABLE_SCHEM(String tABLE_SCHEM) {
+        TABLE_SCHEM = tABLE_SCHEM;
+    }
+
+    public String getTABLE_NAME() {
+        return TABLE_NAME;
+    }
+
+    public void setTABLE_NAME(String tABLE_NAME) {
+        TABLE_NAME = tABLE_NAME;
+    }
+
+    public String getCOLUMN_NAME() {
+        return COLUMN_NAME;
+    }
+
+    public void setCOLUMN_NAME(String cOLUMN_NAME) {
+        COLUMN_NAME = cOLUMN_NAME;
+    }
+
+    public int getDATA_TYPE() {
+        return DATA_TYPE;
+    }
+
+    public void setDATA_TYPE(int dATA_TYPE) {
+        DATA_TYPE = dATA_TYPE;
+    }
+
+    public String getTYPE_NAME() {
+        return TYPE_NAME;
+    }
+
+    public void setTYPE_NAME(String tYPE_NAME) {
+        TYPE_NAME = tYPE_NAME;
+    }
+
+    public int getCOLUMN_SIZE() {
+        return COLUMN_SIZE;
+    }
+
+    public void setCOLUMN_SIZE(int cOLUMN_SIZE) {
+        COLUMN_SIZE = cOLUMN_SIZE;
+    }
+
+    public int getBUFFER_LENGTH() {
+        return BUFFER_LENGTH;
+    }
+
+    public void setBUFFER_LENGTH(int bUFFER_LENGTH) {
+        BUFFER_LENGTH = bUFFER_LENGTH;
+    }
+
+    public int getDECIMAL_DIGITS() {
+        return DECIMAL_DIGITS;
+    }
+
+    public void setDECIMAL_DIGITS(int dECIMAL_DIGITS) {
+        DECIMAL_DIGITS = dECIMAL_DIGITS;
+    }
+
+    public int getNUM_PREC_RADIX() {
+        return NUM_PREC_RADIX;
+    }
+
+    public void setNUM_PREC_RADIX(int nUM_PREC_RADIX) {
+        NUM_PREC_RADIX = nUM_PREC_RADIX;
+    }
+
+    public int getNULLABLE() {
+        return NULLABLE;
+    }
+
+    public void setNULLABLE(int nULLABLE) {
+        NULLABLE = nULLABLE;
+    }
+
+    public String getREMARKS() {
+        return REMARKS;
+    }
+
+    public void setREMARKS(String rEMARKS) {
+        REMARKS = rEMARKS;
+    }
+
+    public String getCOLUMN_DEF() {
+        return COLUMN_DEF;
+    }
+
+    public void setCOLUMN_DEF(String cOLUMN_DEF) {
+        COLUMN_DEF = cOLUMN_DEF;
+    }
+
+    public int getSQL_DATA_TYPE() {
+        return SQL_DATA_TYPE;
+    }
+
+    public void setSQL_DATA_TYPE(int sQL_DATA_TYPE) {
+        SQL_DATA_TYPE = sQL_DATA_TYPE;
+    }
+
+    public int getSQL_DATETIME_SUB() {
+        return SQL_DATETIME_SUB;
+    }
+
+    public void setSQL_DATETIME_SUB(int sQL_DATETIME_SUB) {
+        SQL_DATETIME_SUB = sQL_DATETIME_SUB;
+    }
+
+    public int getCHAR_OCTET_LENGTH() {
+        return CHAR_OCTET_LENGTH;
+    }
+
+    public void setCHAR_OCTET_LENGTH(int cHAR_OCTET_LENGTH) {
+        CHAR_OCTET_LENGTH = cHAR_OCTET_LENGTH;
+    }
+
+    public int getORDINAL_POSITION() {
+        return ORDINAL_POSITION;
+    }
+
+    public void setORDINAL_POSITION(int oRDINAL_POSITION) {
+        ORDINAL_POSITION = oRDINAL_POSITION;
+    }
+
+    public String getIS_NULLABLE() {
+        return IS_NULLABLE;
+    }
+
+    public void setIS_NULLABLE(String iS_NULLABLE) {
+        IS_NULLABLE = iS_NULLABLE;
+    }
+
+    public String getSCOPE_CATLOG() {
+        return SCOPE_CATLOG;
+    }
+
+    public void setSCOPE_CATLOG(String sCOPE_CATLOG) {
+        SCOPE_CATLOG = sCOPE_CATLOG;
+    }
+
+    public String getSCOPE_SCHEMA() {
+        return SCOPE_SCHEMA;
+    }
+
+    public void setSCOPE_SCHEMA(String sCOPE_SCHEMA) {
+        SCOPE_SCHEMA = sCOPE_SCHEMA;
+    }
+
+    public String getSCOPE_TABLE() {
+        return SCOPE_TABLE;
+    }
+
+    public void setSCOPE_TABLE(String sCOPE_TABLE) {
+        SCOPE_TABLE = sCOPE_TABLE;
+    }
+
+    public short getSOURCE_DATA_TYPE() {
+        return SOURCE_DATA_TYPE;
+    }
+
+    public void setSOURCE_DATA_TYPE(short sOURCE_DATA_TYPE) {
+        SOURCE_DATA_TYPE = sOURCE_DATA_TYPE;
+    }
+
+    public String getIS_AUTOINCREMENT() {
+        return IS_AUTOINCREMENT;
+    }
+
+    public void setIS_AUTOINCREMENT(String iS_AUTOINCREMENT) {
+        this.IS_AUTOINCREMENT = iS_AUTOINCREMENT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/4b631f92/server/src/main/java/com/kylinolap/rest/model/Query.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/com/kylinolap/rest/model/Query.java b/server/src/main/java/com/kylinolap/rest/model/Query.java
new file mode 100644
index 0000000..9a1a7cf
--- /dev/null
+++ b/server/src/main/java/com/kylinolap/rest/model/Query.java
@@ -0,0 +1,102 @@
+package com.kylinolap.rest.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class Query {
+    
+    private String name;
+    private String project;
+    private String sql;
+    private String description;
+
+    public Query(){}
+    
+    public Query(String name, String project, String sql, String description) {
+        super();
+        this.name = name;
+        this.project = project;
+        this.sql = sql;
+        this.description = description;
+    }
+
+    public String getId(){
+        return String.valueOf(this.hashCode());
+    }
+ 
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getProject() {
+        return project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getSql() {
+        return sql;
+    }
+
+    public void setSql(String sql) {
+        this.sql = sql;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((description == null) ? 0 : description.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((project == null) ? 0 : project.hashCode());
+        result = prime * result + ((sql == null) ? 0 : sql.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Query other = (Query) obj;
+        if (description == null) {
+            if (other.description != null)
+                return false;
+        } else if (!description.equals(other.description))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (project == null) {
+            if (other.project != null)
+                return false;
+        } else if (!project.equals(other.project))
+            return false;
+        if (sql == null) {
+            if (other.sql != null)
+                return false;
+        } else if (!sql.equals(other.sql))
+            return false;
+        return true;
+    }
+ 
+}