You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eagle.apache.org by ha...@apache.org on 2016/07/21 12:28:54 UTC

[10/11] incubator-eagle git commit: [EAGLE-382][EAGLE-385] Monitoring Application Framework Core

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderLoader.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderLoader.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderLoader.java
new file mode 100644
index 0000000..8b98404
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderLoader.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.service;
+
+import com.typesafe.config.Config;
+import org.apache.eagle.app.service.loader.ApplicationProviderConfigLoader;
+import org.apache.eagle.app.service.loader.ApplicationProviderSPILoader;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class ApplicationProviderLoader {
+    private final Config config;
+    private final Map<String,ApplicationProvider> providers;
+    private final static Logger LOG = LoggerFactory.getLogger(ApplicationProviderLoader.class);
+
+    public ApplicationProviderLoader(Config config) {
+        this.config = config;
+        this.providers = new HashMap<>();
+    }
+
+    public abstract void load();
+
+    protected Config getConfig() {
+        return config;
+    }
+
+    protected void registerProvider(ApplicationProvider provider){
+        if(providers.containsKey(provider.getApplicationDesc().getType())){
+            throw new RuntimeException("Duplicated APPLICATION_TYPE: "+provider.getApplicationDesc().getType()+", was already registered by provider: "+providers.get(provider.getApplicationDesc().getType()));
+        }
+        providers.put(provider.getApplicationDesc().getType(),provider);
+        LOG.info("Initialized application provider: {}",provider);
+    }
+
+    public Collection<ApplicationProvider> getProviders(){
+        return providers.values();
+    }
+
+    public ApplicationProvider<?> getApplicationProviderByType(String type) {
+        return providers.get(type);
+    }
+
+    public void reset(){
+        providers.clear();
+    }
+
+    public static String getDefaultAppProviderLoader(){
+        if(ApplicationProviderConfigLoader
+                .appProviderConfExists(ApplicationProviderConfigLoader.DEFAULT_APPLICATIONS_CONFIG_FILE)){
+            return ApplicationProviderConfigLoader.class.getCanonicalName();
+        } else {
+            return ApplicationProviderSPILoader.class.getCanonicalName();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderService.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderService.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderService.java
new file mode 100644
index 0000000..52dfb5c
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderService.java
@@ -0,0 +1,31 @@
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.service;
+
+import org.apache.eagle.app.Application;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.metadata.service.ApplicationDescService;
+
+import java.util.Collection;
+
+public interface ApplicationProviderService extends ApplicationDescService {
+    void reload();
+    Collection<ApplicationProvider> getProviders();
+    <T extends Application> ApplicationProvider<T> getApplicationProviderByType(String type);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderServiceImpl.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderServiceImpl.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderServiceImpl.java
new file mode 100644
index 0000000..6ce463f
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/ApplicationProviderServiceImpl.java
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.service;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.typesafe.config.Config;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.apache.eagle.metadata.model.ApplicationDesc;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Support to load application provider from application.provider.config = "providers.xml" configuration file
+ * or application.provider.dir = "lib/apps" with SPI Class loader
+ *
+ * TODO: hot-manage application provider loading
+ */
+@Singleton
+public class ApplicationProviderServiceImpl implements ApplicationProviderService {
+    private final Config config;
+    private final static Logger LOG = LoggerFactory.getLogger(ApplicationProviderServiceImpl.class);
+    private final ApplicationProviderLoader appProviderLoader;
+    public final static String APP_PROVIDER_LOADER_CLASS_KEY = "application.provider.loader";
+
+    @Inject
+    public ApplicationProviderServiceImpl(Config config){
+        LOG.info("Initializing {}",this.getClass().getCanonicalName());
+        this.config = config;
+        String appProviderLoaderClass = this.config.hasPath(APP_PROVIDER_LOADER_CLASS_KEY)?
+                this.config.getString(APP_PROVIDER_LOADER_CLASS_KEY):ApplicationProviderLoader.getDefaultAppProviderLoader();
+        LOG.info("Initializing {} = {}",APP_PROVIDER_LOADER_CLASS_KEY,appProviderLoaderClass);
+        appProviderLoader = initializeAppProviderLoader(appProviderLoaderClass);
+        LOG.info("Initialized {}",appProviderLoader);
+        reload();
+    }
+
+    private ApplicationProviderLoader initializeAppProviderLoader(String appProviderLoaderClass){
+        try {
+            return (ApplicationProviderLoader) Class.forName(appProviderLoaderClass).getConstructor(Config.class).newInstance(this.config);
+        } catch (Throwable e) {
+            LOG.error("Failed to initialize ApplicationProviderLoader: "+appProviderLoaderClass,e);
+            throw new IllegalStateException("Failed to initialize ApplicationProviderLoader: "+appProviderLoaderClass,e);
+        }
+    }
+
+    public synchronized void reload(){
+        appProviderLoader.reset();
+        LOG.info("Loading application providers ...");
+        appProviderLoader.load();
+        LOG.info("Loaded {} application providers",appProviderLoader.getProviders().size());
+    }
+
+    public Collection<ApplicationProvider> getProviders(){
+        return appProviderLoader.getProviders();
+    }
+
+    public Collection<ApplicationDesc> getApplicationDescs(){
+        return getProviders().stream().map(ApplicationProvider::getApplicationDesc).collect(Collectors.toList());
+    }
+
+    public ApplicationProvider<?> getApplicationProviderByType(String type) {
+        return appProviderLoader.getApplicationProviderByType(type);
+    }
+
+    @Deprecated
+    public ApplicationDesc getApplicationDescByType(String appType) {
+        return appProviderLoader.getApplicationProviderByType(appType).getApplicationDesc();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderConfigLoader.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderConfigLoader.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderConfigLoader.java
new file mode 100644
index 0000000..11d9905
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderConfigLoader.java
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.service.loader;
+
+import com.google.common.base.Preconditions;
+import com.typesafe.config.Config;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.app.config.ApplicationProvidersConfig;
+import org.apache.eagle.app.service.ApplicationProviderLoader;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+public class ApplicationProviderConfigLoader extends ApplicationProviderLoader {
+    public final static String DEFAULT_APPLICATIONS_CONFIG_FILE = "providers.xml";
+    private final static String APPLICATIONS_CONFIG_PROPS_KEY = "application.provider.config";
+    private final static Logger LOG = LoggerFactory.getLogger(ApplicationProviderConfigLoader.class);
+    public ApplicationProviderConfigLoader(Config config) {
+        super(config);
+    }
+
+    @Override
+    public void load() {
+        List<ApplicationProviderConfig> applicationProviderConfigs = loadProvidersFromProvidersConf();
+        int totalCount = applicationProviderConfigs.size();
+        int loadedCount = 0,failedCount = 0;
+        for(ApplicationProviderConfig providerConfig: applicationProviderConfigs){
+            try {
+                initializeProvider(providerConfig);
+                loadedCount ++;
+            }catch (Throwable ex){
+                LOG.warn("Failed to initialized {}, ignored",providerConfig,ex);
+                failedCount ++;
+            }
+        }
+        LOG.info("Loaded {} app providers (total: {}, failed: {})",loadedCount,totalCount,failedCount);
+    }
+
+    public static boolean appProviderConfExists(String applicationConfFile){
+        InputStream is = ApplicationProviderConfigLoader.class.getResourceAsStream(applicationConfFile);
+        if(is == null){
+            is = ApplicationProviderConfigLoader.class.getResourceAsStream("/"+applicationConfFile);
+        }
+
+        if(is != null){
+            try {
+                return true;
+            } finally {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    LOG.debug(e.getMessage());
+                }
+            }
+        } else {
+            return false;
+        }
+    }
+
+    private void initializeProvider(ApplicationProviderConfig providerConfig) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+        LOG.info("Loading application provider {} from {}",providerConfig.getClassName(),providerConfig.getJarPath());
+        String providerClassName = providerConfig.getClassName();
+        if(providerClassName == null) throw new RuntimeException("provider.classname is null: "+providerConfig);
+        if(providerConfig.getJarPath() == null) throw new RuntimeException("provider.jarpath is null: "+providerConfig);
+
+        Class<?> providerClass = Class.forName(providerClassName);
+
+        if(!ApplicationProvider.class.isAssignableFrom(providerClass)){
+            throw new RuntimeException("providerClassName is not implementation of "+ApplicationProvider.class.getCanonicalName());
+        }
+        ApplicationProvider provider = (ApplicationProvider) providerClass.newInstance();
+        provider.prepare(providerConfig,this.getConfig());
+        Preconditions.checkNotNull(provider.getApplicationDesc(),"appDesc is null");
+        Preconditions.checkNotNull(provider.getApplicationDesc().getType(),"type is null");
+        registerProvider(provider);
+    }
+
+    private List<ApplicationProviderConfig> loadProvidersFromProvidersConf() {
+        String providerConfigFile = DEFAULT_APPLICATIONS_CONFIG_FILE;
+        if(getConfig().hasPath(APPLICATIONS_CONFIG_PROPS_KEY)){
+            providerConfigFile = getConfig().getString(APPLICATIONS_CONFIG_PROPS_KEY);
+            LOG.info("Set {} = {}",APPLICATIONS_CONFIG_PROPS_KEY,providerConfigFile);
+        }
+        InputStream is = null;
+        try {
+            JAXBContext jc = JAXBContext.newInstance(ApplicationProvidersConfig.class);
+            Unmarshaller unmarshaller = jc.createUnmarshaller();
+            is = ApplicationProviderConfigLoader.class.getResourceAsStream(providerConfigFile);
+            if(is == null){
+                is = ApplicationProviderConfigLoader.class.getResourceAsStream("/"+providerConfigFile);
+            }
+            if(is == null){
+                LOG.error("Application provider configuration {} is not found",providerConfigFile);
+            }
+            Preconditions.checkNotNull(is,providerConfigFile+" is not found");
+            return ((ApplicationProvidersConfig) unmarshaller.unmarshal(is)).getProviders();
+        }catch (Exception ex){
+            LOG.error("Failed to load application provider configuration: {}",providerConfigFile,ex);
+            throw new RuntimeException("Failed to load application provider configuration: "+providerConfigFile,ex);
+        } finally {
+            if(is != null) try {
+                is.close();
+            } catch (IOException e) {
+                LOG.error(e.getMessage(),e);
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderSPILoader.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderSPILoader.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderSPILoader.java
new file mode 100644
index 0000000..33cb401
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/service/loader/ApplicationProviderSPILoader.java
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.service.loader;
+
+import com.typesafe.config.Config;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.app.service.ApplicationProviderLoader;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.apache.eagle.app.tools.DynamicJarPathFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ServiceLoader;
+import java.util.function.Function;
+
+public class ApplicationProviderSPILoader extends ApplicationProviderLoader{
+    private final String appProviderExtDir;
+    private final static Logger LOG = LoggerFactory.getLogger(ApplicationProviderSPILoader.class);
+    private final static String APPLICATIONS_DIR_PROPS_KEY = "application.provider.dir";
+
+    public ApplicationProviderSPILoader(Config config) {
+        super(config);
+        if(config.hasPath(APPLICATIONS_DIR_PROPS_KEY)) {
+            this.appProviderExtDir = config.getString(APPLICATIONS_DIR_PROPS_KEY);
+        }else{
+            this.appProviderExtDir = null;
+        }
+
+        LOG.info("Using {}: {}",APPLICATIONS_DIR_PROPS_KEY,this.appProviderExtDir);
+
+    }
+
+    @Override
+    public void load() {
+        if(appProviderExtDir != null) {
+            LOG.info("Loading application providers from class loader of jars in {}", appProviderExtDir);
+            File loc = new File(appProviderExtDir);
+            File[] jarFiles = loc.listFiles(file -> file.getPath().toLowerCase().endsWith(".jar"));
+            if (jarFiles != null) {
+                for (File jarFile : jarFiles) {
+                    try {
+                        URL jarFileUrl = jarFile.toURI().toURL();
+                        LOG.debug("Loading ApplicationProvider from jar: {}", jarFileUrl.toString());
+                        URLClassLoader jarFileClassLoader = new URLClassLoader(new URL[]{jarFileUrl});
+                        loadProviderFromClassLoader(jarFileClassLoader, (applicationProviderConfig) -> jarFileUrl.getPath());
+                    } catch (Exception e) {
+                        LOG.warn("Failed to load application provider from jar {}", jarFile,e);
+                    }
+                }
+            }
+        } else {
+            LOG.info("Loading application providers from context class loader");
+            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+            loadProviderFromClassLoader(classLoader,(applicationProviderConfig) -> DynamicJarPathFinder.findPath(applicationProviderConfig.getClass()));
+        }
+    }
+
+    private void loadProviderFromClassLoader(ClassLoader jarFileClassLoader, Function<ApplicationProviderConfig,String> jarFileSupplier){
+        ServiceLoader<ApplicationProvider> serviceLoader = ServiceLoader.load(ApplicationProvider.class, jarFileClassLoader);
+        for (ApplicationProvider applicationProvider : serviceLoader) {
+            try {
+                ApplicationProviderConfig providerConfig = new ApplicationProviderConfig();
+                providerConfig.setClassName(applicationProvider.getClass().getCanonicalName());
+                providerConfig.setJarPath(jarFileSupplier.apply(providerConfig));
+                applicationProvider.prepare(providerConfig, getConfig());
+                registerProvider(applicationProvider);
+            }catch (Throwable ex){
+                LOG.warn("Failed to register application provider {}",applicationProvider,ex);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/KafkaStreamSink.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/KafkaStreamSink.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/KafkaStreamSink.java
new file mode 100644
index 0000000..eee2a70
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/KafkaStreamSink.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.sink;
+
+import backtype.storm.task.TopologyContext;
+import org.apache.eagle.alert.engine.coordinator.StreamDefinition;
+import org.apache.eagle.alert.engine.model.StreamEvent;
+import org.apache.eagle.app.ApplicationContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class KafkaStreamSink extends StreamSink {
+    private final static Logger LOGGER = LoggerFactory.getLogger(KafkaStreamSink.class);
+    private final String topicId;
+
+    public KafkaStreamSink(StreamDefinition streamDefinition, ApplicationContext applicationContext) {
+        super(streamDefinition, applicationContext);
+        this.topicId = String.format("EAGLE_%s_%s_%s",
+                applicationContext.getAppEntity().getSite().getSiteId(),
+                applicationContext.getAppEntity().getDescriptor().getType(),
+                streamDefinition.getStreamId());
+    }
+
+    @Override
+    public void prepare(Map stormConf, TopologyContext context) {
+        super.prepare(stormConf, context);
+        ensureTopic();
+        // TODO: Create KafkaProducer
+    }
+
+    private void ensureTopic(){
+        LOGGER.info("TODO: ensure kafka topic {} created",this.topicId);
+    }
+
+    @Override
+    protected void onEvent(StreamEvent streamEvent) {
+        LOGGER.info("TODO: producing {}",streamEvent);
+    }
+
+    @Override
+    public Map<String, Object> getSinkContext() {
+        return new HashMap<String,Object>(){
+            {
+                put("kafka.topic",KafkaStreamSink.this.topicId);
+            }
+        };
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/LoggingStreamSink.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/LoggingStreamSink.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/LoggingStreamSink.java
new file mode 100644
index 0000000..ca201a8
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/LoggingStreamSink.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.sink;
+
+import org.apache.eagle.alert.engine.coordinator.StreamDefinition;
+import org.apache.eagle.alert.engine.model.StreamEvent;
+import org.apache.eagle.app.ApplicationContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LoggingStreamSink extends StreamSink {
+    private final static Logger LOGGER = LoggerFactory.getLogger(KafkaStreamSink.class);
+    public LoggingStreamSink(StreamDefinition streamDefinition, ApplicationContext applicationContext) {
+        super(streamDefinition, applicationContext);
+    }
+
+    @Override
+    protected void onEvent(StreamEvent streamEvent) {
+        LOGGER.info("Receiving {}",streamEvent);
+    }
+
+    @Override
+    public Map<String, Object> getSinkContext() {
+        return new HashMap<>();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/StreamSink.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/StreamSink.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/StreamSink.java
new file mode 100644
index 0000000..e0f2db2
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/sink/StreamSink.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.sink;
+
+import backtype.storm.task.TopologyContext;
+import backtype.storm.topology.BasicOutputCollector;
+import backtype.storm.topology.OutputFieldsDeclarer;
+import backtype.storm.topology.base.BaseBasicBolt;
+import backtype.storm.tuple.Fields;
+import backtype.storm.tuple.Tuple;
+import org.apache.eagle.alert.engine.coordinator.StreamDefinition;
+import org.apache.eagle.alert.engine.model.StreamEvent;
+import org.apache.eagle.app.ApplicationContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+
+public abstract class StreamSink extends BaseBasicBolt {
+    private final static Logger LOG = LoggerFactory.getLogger(StreamSink.class);
+    public final static String KEY_FIELD = "KEY";
+    public final static String VALUE_FIELD = "VALUE";
+
+    public StreamSink(StreamDefinition streamDefinition,ApplicationContext applicationContext){
+        
+    }
+
+    @Override
+    public void prepare(Map stormConf, TopologyContext context) {
+        super.prepare(stormConf, context);
+    }
+
+    @Override
+    public void execute(Tuple input, BasicOutputCollector collector) {
+        List<Object> values = input.getValues();
+        Object inputValue;
+        if(values.size() == 1){
+            inputValue = values.get(0);
+        } else if(values.size() == 2){
+            inputValue = values.get(1);
+        } else{
+            collector.reportError(new IllegalStateException("Expect tuple in size of 1: <StreamEvent> or 2: <Object,StreamEvent>, but got "+values.size()+": "+values));
+            return;
+        }
+
+        if(inputValue instanceof StreamEvent){
+            try {
+                onEvent((StreamEvent) inputValue);
+            }catch (Exception e){
+                LOG.error("Failed to execute event {}",inputValue);
+                collector.reportError(e);
+            }
+        } else {
+            LOG.error("{} is not StreamEvent",inputValue);
+            collector.reportError(new IllegalStateException("Input tuple "+input+"is not type of StreamEvent"));
+        }
+    }
+
+    protected abstract void onEvent(StreamEvent streamEvent);
+
+    public abstract Map<String,Object> getSinkContext();
+
+    @Override
+    public void declareOutputFields(OutputFieldsDeclarer declarer) {
+        declarer.declare(new Fields(KEY_FIELD,VALUE_FIELD));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/AbstractApplicationProvider.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/AbstractApplicationProvider.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/AbstractApplicationProvider.java
new file mode 100644
index 0000000..37311eb
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/AbstractApplicationProvider.java
@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.spi;
+
+import com.typesafe.config.Config;
+import org.apache.eagle.alert.engine.coordinator.StreamDefinition;
+import org.apache.eagle.app.Application;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.app.config.ApplicationProviderDescConfig;
+import org.apache.eagle.app.sink.KafkaStreamSink;
+import org.apache.eagle.app.sink.StreamSink;
+import org.apache.eagle.metadata.model.ApplicationDesc;
+import org.apache.eagle.metadata.model.ApplicationDocs;
+import org.apache.eagle.metadata.model.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.bind.JAXBException;
+import java.util.List;
+
+public abstract class AbstractApplicationProvider<T extends Application> implements ApplicationProvider<T> {
+    private final static Logger LOG = LoggerFactory.getLogger(AbstractApplicationProvider.class);
+    private final static String APPLICATIONS_SINK_TYPE_PROPS_KEY = "application.sink.type";
+    private final static String DEFAULT_APPLICATIONS_SINK_TYPE = KafkaStreamSink.class.getCanonicalName();
+    private final ApplicationDesc applicationDesc;
+
+    public AbstractApplicationProvider(){
+        applicationDesc = new ApplicationDesc();
+        applicationDesc.setProviderClass(this.getClass());
+        configure();
+    }
+
+    protected void configure (){
+        // do nothing by default
+    }
+
+    protected AbstractApplicationProvider(String applicationDescConfig) {
+        this();
+        ApplicationProviderDescConfig descWrapperConfig = ApplicationProviderDescConfig.loadFromXML(applicationDescConfig);
+        setType(descWrapperConfig.getType());
+        setVersion(descWrapperConfig.getVersion());
+        setName(descWrapperConfig.getName());
+        setDocs(descWrapperConfig.getDocs());
+        try {
+            if (descWrapperConfig.getAppClass() != null) {
+                setAppClass((Class<T>) Class.forName(descWrapperConfig.getAppClass()));
+                if (!Application.class.isAssignableFrom(applicationDesc.getAppClass())) {
+                    throw new IllegalStateException(descWrapperConfig.getAppClass() + " is not sub-class of " + Application.class.getCanonicalName());
+                }
+            }
+        } catch (ClassNotFoundException e) {
+            LOG.error(e.getMessage(), e);
+            throw new RuntimeException(e.getMessage(), e.getCause());
+        }
+        setViewPath(descWrapperConfig.getViewPath());
+        setConfiguration(descWrapperConfig.getConfiguration());
+        setStreams(descWrapperConfig.getStreams());
+    }
+
+    @Override
+    public void prepare(ApplicationProviderConfig providerConfig, Config envConfig) {
+        this.applicationDesc.setJarPath(providerConfig.getJarPath());
+        String sinkClassName = envConfig.hasPath(APPLICATIONS_SINK_TYPE_PROPS_KEY) ?
+                envConfig.getString(APPLICATIONS_SINK_TYPE_PROPS_KEY) : DEFAULT_APPLICATIONS_SINK_TYPE;
+        try {
+            Class<?> sinkClass = Class.forName(sinkClassName);
+            if(!StreamSink.class.isAssignableFrom(sinkClass)){
+                throw new IllegalStateException(sinkClassName+ "is not assignable from "+StreamSink.class.getCanonicalName());
+            }
+            applicationDesc.setSinkClass(sinkClass);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException(e.getMessage(),e.getCause());
+        }
+    }
+
+    protected void setVersion(String version) {
+        applicationDesc.setVersion(version);
+    }
+
+    protected void setName(String name) {
+        applicationDesc.setName(name);
+    }
+
+    protected void setAppClass(Class<T> appClass) {
+        applicationDesc.setAppClass(appClass);
+    }
+
+    protected void setViewPath(String viewPath) {
+        applicationDesc.setViewPath(viewPath);
+    }
+
+    protected void setConfiguration(Configuration configuration) {
+        applicationDesc.setConfiguration(configuration);
+    }
+
+    protected void setAppConfig(String resourceName) {
+        try {
+            applicationDesc.setConfiguration(Configuration.fromResource(resourceName));
+        } catch (JAXBException e) {
+            LOG.error("Failed to load configuration template from "+resourceName,e);
+            throw new RuntimeException("Failed to load configuration template from "+resourceName,e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "%s[name=%s, type=%s, version=%s, viewPath=%s, appClass=%s, configuration= %s properties]", getClass().getSimpleName(),
+                applicationDesc.getName(),applicationDesc.getType(),applicationDesc.getVersion(), applicationDesc.getViewPath(), applicationDesc.getAppClass(), applicationDesc.getConfiguration().size()
+        );
+    }
+
+    protected void setStreams(List<StreamDefinition> streams) {
+        applicationDesc.setStreams(streams);
+    }
+
+
+    protected void setDocs(ApplicationDocs docs) {
+        applicationDesc.setDocs(docs);
+    }
+
+    public void setType(String type) {
+        applicationDesc.setType(type);
+    }
+
+    @Override
+    public ApplicationDesc getApplicationDesc() {
+        return applicationDesc;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/ApplicationProvider.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/ApplicationProvider.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/ApplicationProvider.java
new file mode 100644
index 0000000..ace0c45
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/spi/ApplicationProvider.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.spi;
+
+import com.typesafe.config.Config;
+import org.apache.eagle.app.Application;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.metadata.model.ApplicationDesc;
+
+public interface ApplicationProvider<T extends Application> {
+
+    void prepare(ApplicationProviderConfig providerConfig,Config envConfig);
+
+    /**
+     * @return application descriptor
+     */
+    ApplicationDesc getApplicationDesc();
+
+    /**
+     * @return application instance
+     */
+    T getApplication();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulator.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulator.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulator.java
new file mode 100644
index 0000000..a3ef0ee
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulator.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.test;
+
+import org.apache.eagle.app.spi.ApplicationProvider;
+
+import java.util.Map;
+
+/**
+ * Application test simulator for developer to quickly run application without diving into application lifecycle
+ */
+public interface AppSimulator {
+    /**
+     *
+     * @param appType
+     */
+    void submit(String appType);
+    /**
+     *
+     * @param appType
+     * @param appConfig
+     */
+    void submit(String appType, Map<String,Object> appConfig);
+
+    /**
+     *
+     * @param appProviderClass
+     */
+    void submit(Class<? extends ApplicationProvider> appProviderClass);
+
+    /**
+     *
+     * @param appProviderClass
+     * @param appConfig
+     */
+    void submit(Class<? extends ApplicationProvider> appProviderClass, Map<String,Object> appConfig) throws Exception;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulatorImpl.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulatorImpl.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulatorImpl.java
new file mode 100644
index 0000000..f5af815
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppSimulatorImpl.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.test;
+
+import com.google.inject.Inject;
+import com.typesafe.config.Config;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.app.resource.ApplicationResource;
+import org.apache.eagle.app.service.AppOperations;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.apache.eagle.app.tools.DynamicJarPathFinder;
+import org.apache.eagle.metadata.model.ApplicationEntity;
+import org.apache.eagle.metadata.model.SiteEntity;
+import org.apache.eagle.metadata.resource.SiteResource;
+import org.junit.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AppSimulatorImpl implements AppSimulator {
+    private final Config config;
+    private final SiteResource siteResource;
+    private final ApplicationResource applicationResource;
+
+    @Inject
+    public AppSimulatorImpl(Config config, SiteResource siteResource,ApplicationResource applicationResource){
+        this.config = config;
+        this.siteResource = siteResource;
+        this.applicationResource = applicationResource;
+    }
+
+    @Override
+    public void submit(String appType) {
+        submit(appType, new HashMap<>());
+    }
+
+    @Override
+    public void submit(String appType, Map<String, Object> appConfig) {
+        SiteEntity siteEntity = getUniqueSite();
+        siteResource.createSite(siteEntity);
+        Assert.assertNotNull(siteEntity.getUuid());
+        // Install application
+        ApplicationEntity applicationEntity = applicationResource.installApplication(new AppOperations.InstallOperation(siteEntity.getSiteId(),appType, ApplicationEntity.Mode.LOCAL));
+        // Start application
+        applicationResource.startApplication(new AppOperations.StartOperation(applicationEntity.getUuid()));
+    }
+
+    private final static AtomicInteger incr = new AtomicInteger();
+
+    private SiteEntity getUniqueSite(){
+        // Create local site
+        SiteEntity siteEntity = new SiteEntity();
+        siteEntity.setSiteId("SIMULATED_SITE_"+incr.incrementAndGet());
+        siteEntity.setSiteName(siteEntity.getSiteId());
+        siteEntity.setDescription("Automatically generated unique simulation site "+siteEntity.getSiteId()+" (simulator: "+this+")");
+        return siteEntity;
+    }
+
+    @Override
+    public void submit(Class<? extends ApplicationProvider> appProviderClass) {
+        submit(appProviderClass, new HashMap<>());
+    }
+
+    @Override
+    public void submit(Class<? extends ApplicationProvider> appProviderClass, Map<String, Object> appConfig) {
+        try {
+            ApplicationProvider applicationProvider = appProviderClass.newInstance();
+            applicationProvider.prepare(new ApplicationProviderConfig(DynamicJarPathFinder.findPath(appProviderClass),appProviderClass),config);
+            submit(applicationProvider.getApplicationDesc().getType(),appConfig);
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new IllegalStateException(e.getMessage(),e);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppTestGuiceModule.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppTestGuiceModule.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppTestGuiceModule.java
new file mode 100644
index 0000000..b2b0194
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppTestGuiceModule.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+import org.apache.eagle.app.ApplicationGuiceModule;
+import org.apache.eagle.common.module.CommonGuiceModule;
+import org.apache.eagle.metadata.persistence.MemoryMetadataStore;
+
+public class AppTestGuiceModule extends AbstractModule{
+    @Override
+    protected void configure() {
+        install(new CommonGuiceModule());
+        install(new ApplicationGuiceModule());
+        install(new MemoryMetadataStore());
+        bind(AppSimulator.class).to(AppSimulatorImpl.class).in(Singleton.class);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppUnitTestRunner.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppUnitTestRunner.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppUnitTestRunner.java
new file mode 100644
index 0000000..2dda4d6
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/AppUnitTestRunner.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class AppUnitTestRunner extends BlockJUnit4ClassRunner {
+    private final Injector injector;
+    public AppUnitTestRunner(Class<?> klass) throws InitializationError {
+        super(klass);
+        injector = createInjectorFor(getModulesFor(klass));
+    }
+
+    @Override
+    protected Object createTest() throws Exception {
+        final Object obj = super.createTest();
+        injector.injectMembers(this);
+        this.injector.injectMembers(obj);
+        return obj;
+    }
+
+    /**
+     * Create a Guice Injector for the class under test.
+     * @param classes Guice Modules
+     * @return A Guice Injector instance.
+     * @throws InitializationError If couldn't instantiate a module.
+     */
+    private Injector createInjectorFor(final Class<?>[] classes)
+            throws InitializationError {
+        final List<Module> modules = new ArrayList<>();
+
+        // Add default modules
+        modules.add(new AppTestGuiceModule());
+
+        if(classes!= null) {
+            for (final Class<?> module : Arrays.asList(classes)) {
+                try {
+                    modules.add((Module) module.newInstance());
+                } catch (final ReflectiveOperationException exception) {
+                    throw new InitializationError(exception);
+                }
+            }
+        }
+        return Guice.createInjector(modules);
+    }
+
+    /**
+     * Get the list of Guice Modules request by GuiceModules annotation in the
+     * class under test.
+     * @param klass Class under test.
+     * @return A Class Array of Guice Modules required by this class.
+     * @throws InitializationError If the annotation is not present.
+     */
+    private Class<?>[] getModulesFor(final Class<?> klass)
+            throws InitializationError {
+        final Modules annotation = klass.getAnnotation(Modules.class);
+        if (annotation == null) {
+            return null;
+        }
+        return annotation.value();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/Modules.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/Modules.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/Modules.java
new file mode 100644
index 0000000..71a6b79
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/Modules.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.test;
+
+import com.google.inject.AbstractModule;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Defines the Guice Modules in use in the test class.
+ *
+ * @version $Id$
+ */
+@Inherited
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Modules {
+    /**
+     * The Guice Modules classes needed by the class under test.
+     */
+    Class<? extends AbstractModule>[] value();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/package-info.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/package-info.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/package-info.java
new file mode 100644
index 0000000..aaaf157
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/test/package-info.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.
+ *
+ *
+ *
+ */
+
+/**
+ * <h1>How to test application ?</h1>
+ *
+ * <h2>Option 1: Test with AppTestRunner</h2>
+ * <pre>
+ * @RunWith(AppTestRunner.class)
+ * public class ExampleApplicationTest {
+ *     @Inject
+ *     private ApplicationResource applicationResource;
+ * }
+ * </pre>
+ *
+ * <h2>Option 2: Manually create injector</h2>
+ * <pre>
+ * public class ExampleApplicationTest {
+ *     @Inject
+ *     private ApplicationResource applicationResource;
+ *
+ *     @Before
+ *     public void setUp(){
+ *         Guice.createInjector(new AppTestModule()).injector.injectMembers(this);
+ *     }
+ * }
+ * </pre>
+ */
+package org.apache.eagle.app.test;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/tools/DynamicJarPathFinder.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/tools/DynamicJarPathFinder.java b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/tools/DynamicJarPathFinder.java
new file mode 100644
index 0000000..fce72fe
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/java/org/apache/eagle/app/tools/DynamicJarPathFinder.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app.tools;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+
+/**
+ * http://stackoverflow.com/questions/1983839/determine-which-jar-file-a-class-is-from
+ * https://github.com/rzwitserloot/lombok.patcher/blob/master/src/lombok/patcher/inject/LiveInjector.java
+ */
+public class DynamicJarPathFinder {
+    private final static Logger LOG = LoggerFactory.getLogger(DynamicJarPathFinder.class);
+    /**
+     * If the provided class has been loaded from a jar file that is on the local file system, will find the absolute path to that jar file.
+     *
+     * @param context The jar file that contained the class file that represents this class will be found. Specify {@code null} to let {@code LiveInjector}
+     *                find its own jar.
+     * @throws IllegalStateException If the specified class was loaded from a directory or in some other way (such as via HTTP, from a database, or some
+     *                               other custom classloading device).
+     */
+    public static String findPathJar(Class<?> context) throws IllegalStateException {
+        if (context == null) context = DynamicJarPathFinder.class;
+        String rawName = context.getName();
+        String classFileName;
+    /* rawName is something like package.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ {
+            int idx = rawName.lastIndexOf('.');
+            classFileName = (idx == -1 ? rawName : rawName.substring(idx+1)) + ".class";
+        }
+
+        String uri = context.getResource(classFileName).toString();
+        if (uri.startsWith("file:")) {
+            throw new IllegalStateException("This class has been loaded from a directory and not from a jar file.");
+        }
+        if (!uri.startsWith("jar:file:")) {
+            int idx = uri.indexOf(':');
+            String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx);
+            throw new IllegalStateException("This class has been loaded remotely via the " + protocol +
+                    " protocol. Only loading from a jar on the local file system is supported.");
+        }
+
+        int idx = uri.indexOf('!');
+        //As far as I know, the if statement below can't ever trigger, so it's more of a sanity check thing.
+        if (idx == -1) throw new IllegalStateException("You appear to have loaded this class from a local jar file, but I can't make sense of the URL!");
+
+        try {
+            String fileName = URLDecoder.decode(uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name());
+            return new File(fileName).getAbsolutePath();
+        } catch (UnsupportedEncodingException e) {
+            throw new InternalError("default charset doesn't exist. Your VM is borked.");
+        }
+    }
+
+    /**
+     * Similar to JarPathFinder, but not make sure the path must valid jar.
+     *
+     * @see DynamicJarPathFinder#findPathJar(Class)
+     * @return the class path contains the context class
+     */
+    public static String findPath(Class<?> context) throws IllegalStateException {
+        if (context == null) context = DynamicJarPathFinder.class;
+        String rawName = context.getName();
+        String classFileName;
+    /* rawName is something like package.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ {
+            int idx = rawName.lastIndexOf('.');
+            classFileName = (idx == -1 ? rawName : rawName.substring(idx+1)) + ".class";
+        }
+
+        String uri = context.getResource(classFileName).toString();
+        if (uri.startsWith("file:")) {
+            LOG.warn("This class has been loaded from a directory and not from a jar file: {}",uri);
+            String fileName = null;
+            try {
+                fileName = URLDecoder.decode(uri.substring("file:".length(), uri.length()), Charset.defaultCharset().name());
+                return new File(fileName).getAbsolutePath();
+            } catch (UnsupportedEncodingException e) {
+                throw new InternalError("default charset doesn't exist. Your VM is borked.");
+            }
+        }
+
+        if (!uri.startsWith("jar:file:")) {
+            int idx = uri.indexOf(':');
+            String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx);
+            throw new IllegalStateException("This class has been loaded remotely via the " + protocol +
+                    " protocol. Only loading from a jar on the local file system is supported.");
+        }
+
+        int idx = uri.indexOf('!');
+        //As far as I know, the if statement below can't ever trigger, so it's more of a sanity check thing.
+        if (idx == -1) {
+            throw new IllegalStateException("You appear to have loaded this class from a local jar file, but I can't make sense of the URL!");
+        }
+
+        try {
+            String fileName = URLDecoder.decode(uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name());
+            return new File(fileName).getAbsolutePath();
+        } catch (UnsupportedEncodingException e) {
+            throw new InternalError("default charset doesn't exist. Your VM is borked.");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/resources/applications.xml
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/resources/applications.xml b/eagle-core/eagle-app/eagle-app-base/src/main/resources/applications.xml
new file mode 100644
index 0000000..5f67807
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/resources/applications.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You 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.
+  -->
+
+<applications>
+    <application>
+        <type>EXAMPLE_APP</type>
+        <version>0.2.0</version>
+        <name>Example Application</name>
+        <jarPath>target/apache-eagle-example-app.jar</jarPath>
+        <className>org.apache.eagle.app.base.example.ExampleApplication</className>
+        <!-- 'view' provides UI elements like portal/widget/dashboard, etc. -->
+        <viewPath>webapp/app/example</viewPath>
+        <configuration>
+            <property>
+                <name>kafka.topic</name>
+                <value>hdfs_audit</value>
+                <description>Kafka Topic</description>
+            </property>
+            <property>
+                <name>zookeeper.server</name>
+                <displayName>Zookeeper Server</displayName>
+                <value>localhost:2181</value>
+                <description>Zookeeper Server address</description>
+            </property>
+        </configuration>
+    </application>
+</applications>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/main/resources/log4j.properties b/eagle-core/eagle-app/eagle-app-base/src/main/resources/log4j.properties
new file mode 100644
index 0000000..fb13ad5
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/main/resources/log4j.properties
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+log4j.rootLogger=DEBUG, stdout
+
+# standard output
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %p [%t] %c{2}[%L]: %m%n
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderDescConfigTest.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderDescConfigTest.java b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderDescConfigTest.java
new file mode 100644
index 0000000..5a6980c
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderDescConfigTest.java
@@ -0,0 +1,53 @@
+package org.apache.eagle.app;
+
+import org.apache.eagle.app.config.ApplicationProviderDescConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import java.io.InputStream;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.
+ */
+public class ApplicationProviderDescConfigTest {
+    @Test
+    public void testApplicationDescWrapperConfigLoadFromXML(){
+        ApplicationProviderDescConfig config = ApplicationProviderDescConfig.loadFromXML("TestApplicationMetadata.xml");
+        Assert.assertNotNull(config);
+    }
+
+    @Test
+    public void testStreamDefinitionLoadFromXML(){
+        String configXmlFile = "TestStreamDefinitionConf.xml";
+        try {
+            JAXBContext jc = JAXBContext.newInstance(StreamDefinitions.class);
+            Unmarshaller unmarshaller = jc.createUnmarshaller();
+            InputStream is = ApplicationProviderDescConfigTest.class.getResourceAsStream(configXmlFile);
+            if(is == null){
+                is = ApplicationProviderDescConfigTest.class.getResourceAsStream("/"+configXmlFile);
+            }
+            if(is == null){
+                throw new IllegalStateException("Stream Definition configuration "+configXmlFile+" is not found");
+            }
+            StreamDefinitions streamDefinitions = (StreamDefinitions) unmarshaller.unmarshal(is);
+            Assert.assertNotNull(streamDefinitions);
+        } catch (Exception ex){
+            throw new RuntimeException("Failed to load application descriptor configuration: "+configXmlFile,ex);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderServiceTest.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderServiceTest.java b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderServiceTest.java
new file mode 100644
index 0000000..61f7542
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ApplicationProviderServiceTest.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app;
+
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.apache.eagle.app.config.ApplicationProviderConfig;
+import org.apache.eagle.app.service.ApplicationProviderService;
+import org.apache.eagle.app.spi.ApplicationProvider;
+import org.apache.eagle.common.module.CommonGuiceModule;
+import org.apache.eagle.metadata.model.ApplicationDesc;
+import org.apache.eagle.metadata.persistence.MemoryMetadataStore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+public class ApplicationProviderServiceTest {
+    private final static Logger LOGGER = LoggerFactory.getLogger(ApplicationProviderServiceTest.class);
+    private Injector injector = Guice.createInjector(new CommonGuiceModule(),new ApplicationGuiceModule(), MemoryMetadataStore.getInstance());
+
+    @Test
+    public void testApplicationProviderManagerInit(){
+        ApplicationProviderService providerManager = injector.getInstance(ApplicationProviderService.class);
+        Collection<ApplicationDesc> applicationDescs = providerManager.getApplicationDescs();
+        Collection<ApplicationProvider> applicationProviders = providerManager.getProviders();
+
+        applicationDescs.forEach((d)-> LOGGER.debug(d.toString()));
+        applicationProviders.forEach((d)-> LOGGER.debug(d.toString()));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ConfigurationHelperTest.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ConfigurationHelperTest.java b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ConfigurationHelperTest.java
new file mode 100644
index 0000000..ebdae61
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/ConfigurationHelperTest.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app;
+
+import org.apache.eagle.metadata.model.Configuration;
+import org.apache.eagle.metadata.utils.ConfigTemplateHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.xml.bind.JAXBException;
+
+public class ConfigurationHelperTest {
+    @Test
+    public void testConfigTemplateUnmarshall() throws JAXBException {
+        Configuration configuration = ConfigTemplateHelper.unmarshallFromResource("/config_template.xml");
+        Assert.assertNotNull(configuration);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/StreamDefinitions.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/StreamDefinitions.java b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/StreamDefinitions.java
new file mode 100644
index 0000000..7ca0176
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/StreamDefinitions.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app;
+
+import org.apache.eagle.alert.engine.coordinator.StreamDefinition;
+
+import javax.xml.bind.annotation.*;
+import java.util.List;
+
+@XmlRootElement(name = "streams")
+public class StreamDefinitions{
+
+    private List<StreamDefinition> streams;
+
+    @XmlElement(name = "stream")
+    public List<StreamDefinition> getStreams() {
+        return streams;
+    }
+
+    public void setStreams(List<StreamDefinition> streams) {
+        this.streams = streams;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationImpl.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationImpl.java b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationImpl.java
new file mode 100644
index 0000000..90a2b90
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 org.apache.eagle.app;
+
+import backtype.storm.spout.SpoutOutputCollector;
+import backtype.storm.task.TopologyContext;
+import backtype.storm.topology.OutputFieldsDeclarer;
+import backtype.storm.topology.TopologyBuilder;
+import backtype.storm.topology.base.BaseRichSpout;
+import backtype.storm.tuple.Fields;
+import backtype.storm.tuple.Values;
+import org.apache.eagle.app.spi.AbstractApplicationProvider;
+import org.junit.Ignore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+@Ignore
+public class TestApplicationImpl extends AbstractApplication {
+    private final static Logger LOG = LoggerFactory.getLogger(TestApplicationImpl.class);
+    public class RandomEventSpout extends BaseRichSpout {
+        SpoutOutputCollector _collector;
+
+        @SuppressWarnings("rawtypes")
+        @Override
+        public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
+            _collector = collector;
+        }
+
+        @Override
+        public void nextTuple() {
+
+        }
+
+        @Override
+        public void ack(Object id) {
+            //Ignored
+        }
+
+        @Override
+        public void fail(Object id) {
+            _collector.emit(new Values(id), id);
+        }
+
+        @Override
+        public void declareOutputFields(OutputFieldsDeclarer declarer) {
+            declarer.declare(new Fields("key","event"));
+        }
+    }
+
+    protected void buildTopology(TopologyBuilder builder, ApplicationContext context) {
+        builder.setSpout("mockMetricSpout", new RandomEventSpout(), 4);
+        builder.setBolt("sink_1",context.getStreamSink("TEST_STREAM_1")).fieldsGrouping("mockMetricSpout",new Fields("key"));
+        builder.setBolt("sink_2",context.getStreamSink("TEST_STREAM_2")).fieldsGrouping("mockMetricSpout",new Fields("key"));
+    }
+
+    public static class Provider extends AbstractApplicationProvider<TestApplicationImpl> {
+        public Provider(){
+            super("TestApplicationMetadata.xml");
+        }
+
+        @Override
+        public TestApplicationImpl getApplication() {
+            return new TestApplicationImpl();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationTestSuite.java
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationTestSuite.java b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationTestSuite.java
new file mode 100644
index 0000000..f86f903
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/java/org/apache/eagle/app/TestApplicationTestSuite.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.eagle.app;
+
+import com.google.inject.Inject;
+import org.apache.eagle.app.resource.ApplicationResource;
+import org.apache.eagle.app.service.AppOperations;
+import org.apache.eagle.app.test.AppSimulator;
+import org.apache.eagle.app.test.AppUnitTestRunner;
+import org.apache.eagle.metadata.model.ApplicationDesc;
+import org.apache.eagle.metadata.model.ApplicationEntity;
+import org.apache.eagle.metadata.model.SiteEntity;
+import org.apache.eagle.metadata.resource.SiteResource;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+
+@RunWith(AppUnitTestRunner.class)
+public class TestApplicationTestSuite {
+    @Inject private SiteResource siteResource;
+    @Inject private ApplicationResource applicationResource;
+    @Inject private AppSimulator simulator;
+
+    @Test
+    public void testApplicationProviderLoading(){
+        Collection<ApplicationDesc> applicationDescs = applicationResource.getApplicationDescs();
+        Assert.assertNotNull(applicationDescs);
+        Assert.assertEquals(1,applicationDescs.size());
+    }
+
+    @Test
+    public void testApplicationLifecycle() throws InterruptedException {
+        // Create local site
+        SiteEntity siteEntity = new SiteEntity();
+        siteEntity.setSiteId("test_site");
+        siteEntity.setSiteName("Test Site");
+        siteEntity.setDescription("Test Site for ExampleApplicationTest");
+        siteResource.createSite(siteEntity);
+        Assert.assertNotNull(siteEntity.getUuid());
+
+        // Install application
+        ApplicationEntity applicationEntity = applicationResource.installApplication(new AppOperations.InstallOperation("test_site","TEST_APPLICATION", ApplicationEntity.Mode.LOCAL));
+        // Start application
+        applicationResource.startApplication(new AppOperations.StartOperation(applicationEntity.getUuid()));
+        // Stop application
+        applicationResource.stopApplication(new AppOperations.StopOperation(applicationEntity.getUuid()));
+        // Uninstall application
+        applicationResource.uninstallApplication(new AppOperations.UninstallOperation(applicationEntity.getUuid()));
+        try {
+            applicationResource.getApplicationEntityByUUID(applicationEntity.getUuid());
+            Assert.fail("Application instance (UUID: "+applicationEntity.getUuid()+") should have been uninstalled");
+        } catch (Exception ex){
+            // Expected exception
+        }
+    }
+
+    @Test
+    public void testApplicationQuickRunWithAppType(){
+        simulator.submit("TEST_APPLICATION");
+    }
+
+    @Test
+    public void testApplicationQuickRunWithAppProvider(){
+        simulator.submit(TestApplicationImpl.Provider.class);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/e21b073f/eagle-core/eagle-app/eagle-app-base/src/test/resources/ExampleApplicationConf.xml
----------------------------------------------------------------------
diff --git a/eagle-core/eagle-app/eagle-app-base/src/test/resources/ExampleApplicationConf.xml b/eagle-core/eagle-app/eagle-app-base/src/test/resources/ExampleApplicationConf.xml
new file mode 100644
index 0000000..4a31c5e
--- /dev/null
+++ b/eagle-core/eagle-app/eagle-app-base/src/test/resources/ExampleApplicationConf.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You 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.
+  -->
+<configuration>
+    <property>
+        <name>kafka.topic</name>
+        <displayName>Kafka Topic</displayName>
+        <value>hdfs_audit</value>
+        <description>Kafka Topic</description>
+    </property>
+    <property>
+        <name>zookeeper.server</name>
+        <displayName>Zookeeper Server</displayName>
+        <value>localhost:2181</value>
+        <description>Zookeeper Server address</description>
+    </property>
+</configuration>
\ No newline at end of file