You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bf...@apache.org on 2013/02/13 23:04:21 UTC

[22/50] [abbrv] Moved console-proxy into services

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java
new file mode 100644
index 0000000..15cf451
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java
@@ -0,0 +1,88 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * 
+ * ConsoleProxyClientStatsCollector collects client stats for console proxy agent to report
+ */
+public class ConsoleProxyClientStatsCollector {
+    
+    ArrayList<ConsoleProxyConnection> connections;
+    
+    public ConsoleProxyClientStatsCollector() {
+    }
+    
+    public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
+        setConnections(connMap);
+    }
+    
+    public String getStatsReport() {
+        Gson gson = new GsonBuilder().setPrettyPrinting().create();
+        return gson.toJson(this);
+    }
+    
+    public void getStatsReport(OutputStreamWriter os) {
+        Gson gson = new GsonBuilder().setPrettyPrinting().create();
+        gson.toJson(this, os);
+    }
+
+    private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
+        
+        ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
+        Enumeration<String> e = connMap.keys();
+        while (e.hasMoreElements()) {
+            synchronized (connMap) {
+                String key = e.nextElement();
+                ConsoleProxyClient client = connMap.get(key);
+                 
+                ConsoleProxyConnection conn = new ConsoleProxyConnection();
+                 
+                conn.id = client.getClientId();
+                conn.clientInfo = "";
+                conn.host = client.getClientHostAddress();
+                conn.port = client.getClientHostPort();
+                conn.tag = client.getClientTag();
+                conn.createTime = client.getClientCreateTime();
+                conn.lastUsedTime = client.getClientLastFrontEndActivityTime();
+                conns.add(conn);
+            }
+        }
+        connections = conns;
+    }
+    
+    public static class ConsoleProxyConnection {
+        public int id;
+        public String clientInfo;
+        public String host;
+        public int port;
+        public String tag;
+        public long createTime;
+        public long lastUsedTime;
+        
+        public ConsoleProxyConnection() {
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java
new file mode 100644
index 0000000..408eb04
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java
@@ -0,0 +1,70 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class ConsoleProxyCmdHandler implements HttpHandler {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyCmdHandler.class);
+    
+    public void handle(HttpExchange t) throws IOException {
+        try {
+            Thread.currentThread().setName("Cmd Thread " + 
+                    Thread.currentThread().getId() + " " + t.getRemoteAddress());
+            s_logger.info("CmdHandler " + t.getRequestURI());
+            doHandle(t);
+        } catch (Exception e) {
+            s_logger.error(e.toString(), e);
+            String response = "Not found";
+            t.sendResponseHeaders(404, response.length());
+            OutputStream os = t.getResponseBody();
+            os.write(response.getBytes());
+            os.close();
+        } catch(OutOfMemoryError e) {
+            s_logger.error("Unrecoverable OutOfMemory Error, exit and let it be re-launched");
+            System.exit(1);
+        } catch (Throwable e) {
+            s_logger.error(e.toString(), e);
+        } finally {
+            t.close();
+        }
+    }
+    
+    public void doHandle(HttpExchange t) throws Exception {
+        String path = t.getRequestURI().getPath();
+        int i = path.indexOf("/", 1);
+        String cmd = path.substring(i + 1);
+        s_logger.info("Get CMD request for " + cmd);
+        if (cmd.equals("getstatus")) {
+            ConsoleProxyClientStatsCollector statsCollector = ConsoleProxy.getStatsCollector();
+            
+            Headers hds = t.getResponseHeaders();
+            hds.set("Content-Type", "text/plain");
+            t.sendResponseHeaders(200, 0);
+            OutputStreamWriter os = new OutputStreamWriter(t.getResponseBody());
+            statsCollector.getStatsReport(os);
+            os.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java
new file mode 100644
index 0000000..7f82a96
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java
@@ -0,0 +1,109 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.log4j.Logger;
+
+/**
+ * 
+ * ConsoleProxyGCThread does house-keeping work for the process, it helps cleanup log files,
+ * recycle idle client sessions without front-end activities and report client stats to external
+ * management software 
+ */
+public class ConsoleProxyGCThread extends Thread {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyGCThread.class);
+    
+    private final static int MAX_SESSION_IDLE_SECONDS = 180;
+
+    private Hashtable<String, ConsoleProxyClient> connMap;
+    private long lastLogScan = 0;
+    
+    public ConsoleProxyGCThread(Hashtable<String, ConsoleProxyClient> connMap) {
+        this.connMap = connMap;
+    }
+    
+    private void cleanupLogging() {
+        if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000)
+            return;
+        
+        lastLogScan = System.currentTimeMillis();
+        
+        File logDir = new File("./logs");
+        File files[] = logDir.listFiles();
+        if(files != null) {
+            for(File file : files) {
+                if(System.currentTimeMillis() - file.lastModified() >= 86400000L) {
+                    try {
+                        file.delete();
+                    } catch(Throwable e) {
+                    }
+                }
+            }
+        }
+    }
+    
+    @Override
+    public void run() {
+        
+        boolean bReportLoad = false;
+        while (true) {
+            cleanupLogging();
+            bReportLoad = false;
+            
+            if(s_logger.isDebugEnabled())
+                s_logger.debug("connMap=" + connMap);
+            Enumeration<String> e = connMap.keys();
+            while (e.hasMoreElements()) {
+                String key;
+                ConsoleProxyClient client;
+                
+                synchronized (connMap) {
+                    key  = e.nextElement();
+                    client  = connMap.get(key);
+                }
+
+                long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000;
+                if (seconds_unused < MAX_SESSION_IDLE_SECONDS) {
+                    continue;
+                }
+                
+                synchronized (connMap) {
+                    connMap.remove(key);
+                    bReportLoad = true;
+                }
+                
+                // close the server connection
+                s_logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds");
+                client.closeClient();
+            }
+            
+            if(bReportLoad) {
+                // report load changes
+                String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport(); 
+                ConsoleProxy.reportLoadInfo(loadInfo);
+                if(s_logger.isDebugEnabled())
+                    s_logger.debug("Report load change : " + loadInfo);
+            }
+            
+            try { Thread.sleep(5000); } catch (InterruptedException ex) {}
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
new file mode 100644
index 0000000..7756d01
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.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
+//
+//   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.cloud.consoleproxy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+
+public class ConsoleProxyHttpHandlerHelper {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyHttpHandlerHelper.class);
+    
+    public static Map<String, String> getQueryMap(String query) {
+        String[] params = query.split("&");
+        Map<String, String> map = new HashMap<String, String>();
+        for (String param : params) {
+            String[] paramTokens = param.split("=");
+            if(paramTokens != null && paramTokens.length == 2) {
+                String name = param.split("=")[0];
+                String value = param.split("=")[1];
+                map.put(name, value);
+            } else if (paramTokens.length == 3) {
+                // very ugly, added for Xen tunneling url
+                String name = paramTokens[0];
+                String value = paramTokens[1] + "=" + paramTokens[2];
+                map.put(name, value);
+            } else {
+                if(s_logger.isDebugEnabled())
+                    s_logger.debug("Invalid paramemter in URL found. param: " + param);
+            }
+        }
+        
+        // This is a ugly solution for now. We will do encryption/decryption translation
+        // here to make it transparent to rest of the code.
+        if(map.get("token") != null) {
+            ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(
+                ConsoleProxy.getEncryptorPassword());
+            
+            ConsoleProxyClientParam param = encryptor.decryptObject(ConsoleProxyClientParam.class, map.get("token"));
+            if(param != null) {
+                if(param.getClientHostAddress() != null)
+                    map.put("host", param.getClientHostAddress());
+                if(param.getClientHostPort() != 0)
+                    map.put("port", String.valueOf(param.getClientHostPort()));
+                if(param.getClientTag() != null)
+                    map.put("tag", param.getClientTag());
+                if(param.getClientHostPassword() != null)
+                    map.put("sid", param.getClientHostPassword());
+                if(param.getClientTunnelUrl() != null)
+                    map.put("consoleurl", param.getClientTunnelUrl());
+                if(param.getClientTunnelSession() != null)
+                    map.put("sessionref", param.getClientTunnelSession());
+                if(param.getTicket() != null)
+                    map.put("ticket", param.getTicket());
+            }
+        }
+        
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java
new file mode 100644
index 0000000..ff66de3
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.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
+//
+//   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.cloud.consoleproxy;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.cloud.consoleproxy.util.LoggerFactory;
+
+public class ConsoleProxyLoggerFactory implements LoggerFactory {
+    public ConsoleProxyLoggerFactory() {
+    }
+    
+    public Logger getLogger(Class<?> clazz) {
+        return new Log4jLogger(org.apache.log4j.Logger.getLogger(clazz));
+    }
+    
+    public static class Log4jLogger extends Logger {
+        private org.apache.log4j.Logger logger;
+        
+        public Log4jLogger(org.apache.log4j.Logger logger) {
+            this.logger = logger;
+        }
+        
+        public boolean isTraceEnabled() {
+            return logger.isTraceEnabled();
+        }
+        
+        public boolean isDebugEnabled() {
+            return logger.isDebugEnabled();
+        }
+        
+        public boolean isInfoEnabled() {
+            return logger.isInfoEnabled();
+        }
+
+        public void trace(Object message) {
+            logger.trace(message);
+        }
+        
+        public void trace(Object message, Throwable exception) {
+            logger.trace(message, exception);
+        }
+        
+        public void info(Object message) {
+            logger.info(message);
+        }
+        
+        public void info(Object message, Throwable exception) {
+            logger.info(message, exception);
+        }
+        
+        public void debug(Object message) {
+            logger.debug(message);
+        }
+        
+        public void debug(Object message, Throwable exception) {
+            logger.debug(message, exception);
+        }
+        
+        public void warn(Object message) {
+            logger.warn(message);
+        }
+        
+        public void warn(Object message, Throwable exception) {
+            logger.warn(message, exception);
+        }
+        
+        public void error(Object message) {
+            logger.error(message);
+        }
+        
+        public void error(Object message, Throwable exception) {
+            logger.error(message, exception);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java
new file mode 100644
index 0000000..030b2f4
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyMonitor.java
@@ -0,0 +1,153 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.xml.DOMConfigurator;
+
+import com.cloud.consoleproxy.util.Logger;
+
+
+//
+//
+// I switched to a simpler solution to monitor only unrecoverable exceptions, under these cases, console proxy process will exit
+// itself and the shell script will re-launch console proxy
+//
+public class ConsoleProxyMonitor {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyMonitor.class);
+    
+    private String[] _argv;
+    private Map<String, String> _argMap = new HashMap<String, String>();
+    
+    private volatile Process _process;
+    private boolean _quit = false;
+    
+    public ConsoleProxyMonitor(String[] argv) {
+        _argv = argv;
+        
+        for(String arg : _argv) {
+            String[] tokens = arg.split("=");
+            if(tokens.length == 2) {
+                s_logger.info("Add argument " + tokens[0] + "=" + tokens[1] + " to the argument map");
+
+                _argMap.put(tokens[0].trim(), tokens[1].trim());
+            } else {
+                s_logger.warn("unrecognized argument, skip adding it to argument map");
+            }
+        }
+    }
+    
+    private void run() {
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                _quit = true;
+                onShutdown();
+            }
+        });
+        
+        while(!_quit) {
+            String cmdLine = getLaunchCommandLine();
+            
+            s_logger.info("Launch console proxy process with command line: " + cmdLine);
+            
+            try {
+                _process = Runtime.getRuntime().exec(cmdLine);
+            } catch (IOException e) {
+                s_logger.error("Unexpected exception ", e);
+                System.exit(1);
+            }
+            
+            boolean waitSucceeded = false;
+            int exitCode = 0;
+            while(!waitSucceeded) {
+                try {
+                    exitCode = _process.waitFor();
+                    waitSucceeded = true;
+                    
+                    if(s_logger.isInfoEnabled())
+                        s_logger.info("Console proxy process exits with code: " + exitCode);
+                } catch (InterruptedException e) {
+                    if(s_logger.isInfoEnabled())
+                        s_logger.info("InterruptedException while waiting for termination of console proxy, will retry");
+                }
+            }
+        }
+    }
+    
+    private String getLaunchCommandLine() {
+        StringBuffer sb = new StringBuffer("java ");
+        String jvmOptions = _argMap.get("jvmoptions");
+        
+        if(jvmOptions != null)
+            sb.append(jvmOptions);
+        
+        for(Map.Entry<String, String> entry : _argMap.entrySet()) {
+            if(!"jvmoptions".equalsIgnoreCase(entry.getKey()))
+                sb.append(" ").append(entry.getKey()).append("=").append(entry.getValue());
+        }
+        
+        return sb.toString();
+    }
+    
+    private void onShutdown() {
+        if(_process != null) {
+            if(s_logger.isInfoEnabled())
+                s_logger.info("Console proxy monitor shuts dwon, terminate console proxy process");
+            _process.destroy();
+        }
+    }
+
+    private static void configLog4j() {
+        URL configUrl = System.class.getResource("/conf/log4j-cloud.xml");
+        if(configUrl == null)
+            configUrl = ClassLoader.getSystemResource("log4j-cloud.xml");
+        
+        if(configUrl == null)
+            configUrl = ClassLoader.getSystemResource("conf/log4j-cloud.xml");
+            
+        if(configUrl != null) {
+            try {
+                System.out.println("Configure log4j using " + configUrl.toURI().toString());
+            } catch (URISyntaxException e1) {
+                e1.printStackTrace();
+            }
+
+            try {
+                File file = new File(configUrl.toURI());
+                
+                System.out.println("Log4j configuration from : " + file.getAbsolutePath());
+                DOMConfigurator.configureAndWatch(file.getAbsolutePath(), 10000);
+            } catch (URISyntaxException e) {
+                System.out.println("Unable to convert log4j configuration Url to URI");
+            }
+        } else {
+            System.out.println("Configure log4j with default properties");
+        }
+    }
+    
+    public static void main(String[] argv) {
+        configLog4j();
+        (new ConsoleProxyMonitor(argv)).run();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java
new file mode 100644
index 0000000..29826f0
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java
@@ -0,0 +1,142 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * 
+ * A simple password based encyrptor based on DES. It can serialize simple POJO object into URL safe string
+ * and deserialize it back.
+ * 
+ */
+public class ConsoleProxyPasswordBasedEncryptor {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
+    
+    private String password;
+    private Gson gson;
+    
+    public ConsoleProxyPasswordBasedEncryptor(String password) {
+        this.password = password;
+        gson = new GsonBuilder().create();
+    }
+    
+    public String encryptText(String text) {
+        if(text == null || text.isEmpty())
+            return text;
+        
+        assert(password != null);
+        assert(!password.isEmpty());
+        
+        try {
+            Cipher cipher = Cipher.getInstance("DES");
+            int maxKeySize = 8;
+            SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+            byte[] encryptedBytes = cipher.doFinal(text.getBytes());
+            return Base64.encodeBase64URLSafeString(encryptedBytes);
+        } catch (NoSuchAlgorithmException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (NoSuchPaddingException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (IllegalBlockSizeException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (BadPaddingException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (InvalidKeyException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        }
+    }
+
+    public String decryptText(String encryptedText) {
+        if(encryptedText == null || encryptedText.isEmpty())
+            return encryptedText;
+
+        assert(password != null);
+        assert(!password.isEmpty());
+
+        try {
+            Cipher cipher = Cipher.getInstance("DES");
+            int maxKeySize = 8;
+            SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
+            cipher.init(Cipher.DECRYPT_MODE, keySpec);
+            
+            byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
+            return new String(cipher.doFinal(encryptedBytes));
+        } catch (NoSuchAlgorithmException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (NoSuchPaddingException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (IllegalBlockSizeException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (BadPaddingException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        } catch (InvalidKeyException e) {
+            s_logger.error("Unexpected exception ", e);
+            return null;
+        }
+    }
+    
+    public <T> String encryptObject(Class<?> clz, T obj) {
+        if(obj == null)
+            return null;
+        
+        String json = gson.toJson(obj);
+        return encryptText(json);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public <T> T decryptObject(Class<?> clz, String encrypted) {
+        if(encrypted == null || encrypted.isEmpty())
+            return null;
+        
+        String json = decryptText(encrypted);
+        return (T)gson.fromJson(json, clz);
+    }
+    
+    private static byte[] normalizeKey(byte[] keyBytes, int keySize) {
+        assert(keySize > 0);
+        byte[] key = new byte[keySize];
+        
+        for(int i = 0; i < keyBytes.length; i++)
+            key[i%keySize] ^= keyBytes[i];
+        
+        return key;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
new file mode 100644
index 0000000..7d16047
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java
@@ -0,0 +1,181 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class ConsoleProxyResourceHandler implements HttpHandler {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyResourceHandler.class);
+
+    static Map<String, String> s_mimeTypes;
+    static {
+        s_mimeTypes = new HashMap<String, String>();
+        s_mimeTypes.put("jar", "application/java-archive");
+        s_mimeTypes.put("js", "text/javascript");
+        s_mimeTypes.put("css", "text/css");
+        s_mimeTypes.put("jpg", "image/jpeg");
+        s_mimeTypes.put("html", "text/html");
+        s_mimeTypes.put("htm", "text/html");
+        s_mimeTypes.put("log", "text/plain");
+    }
+    
+    static Map<String, String> s_validResourceFolders;
+    static {
+        s_validResourceFolders = new HashMap<String, String>();
+        s_validResourceFolders.put("applet", "");
+        s_validResourceFolders.put("logs", "");
+        s_validResourceFolders.put("images", "");
+        s_validResourceFolders.put("js", "");
+        s_validResourceFolders.put("css", "");
+        s_validResourceFolders.put("html", "");
+    }
+    
+    public ConsoleProxyResourceHandler() {
+    }
+    
+    public void handle(HttpExchange t) throws IOException {
+        try {
+            if(s_logger.isDebugEnabled())
+                s_logger.debug("Resource Handler " + t.getRequestURI());
+            
+            long startTick = System.currentTimeMillis();
+            
+            doHandle(t);
+            
+            if(s_logger.isDebugEnabled())
+                s_logger.debug(t.getRequestURI() + " Process time " + (System.currentTimeMillis() - startTick) + " ms");
+        } catch (IOException e) {
+            throw e;
+        } catch(Throwable e) {
+            s_logger.error("Unexpected exception, ", e);
+            t.sendResponseHeaders(500, -1);     // server error
+        } finally {
+            t.close();
+        }
+    }
+    
+    @SuppressWarnings("deprecation")
+    private void doHandle(HttpExchange t) throws Exception {
+        String path = t.getRequestURI().getPath();
+
+        if(s_logger.isInfoEnabled())
+            s_logger.info("Get resource request for " + path);
+        
+        int i = path.indexOf("/", 1);
+        String filepath = path.substring(i + 1);
+        i = path.lastIndexOf(".");
+        String extension = (i == -1) ? "" : path.substring(i + 1);
+        String contentType = getContentType(extension);
+
+        if(!validatePath(filepath)) {
+            if(s_logger.isInfoEnabled())
+                s_logger.info("Resource access is forbidden, uri: " + path);
+            
+            t.sendResponseHeaders(403, -1);     // forbidden
+            return;
+        }
+        
+        File f = new File ("./" + filepath);
+        if(f.exists()) {
+            long lastModified = f.lastModified();
+            String ifModifiedSince = t.getRequestHeaders().getFirst("If-Modified-Since");
+            if (ifModifiedSince != null) {
+                long d = Date.parse(ifModifiedSince);
+                if (d + 1000 >= lastModified) {
+                    Headers hds = t.getResponseHeaders();
+                    hds.set("Content-Type", contentType);
+                    t.sendResponseHeaders(304, -1);
+                    
+                    if(s_logger.isInfoEnabled())
+                        s_logger.info("Sent 304 file has not been " +
+                                "modified since " + ifModifiedSince);
+                    return;
+                }
+            }
+            
+            long length = f.length();
+            Headers hds = t.getResponseHeaders();
+            hds.set("Content-Type", contentType);
+            hds.set("Last-Modified", new Date(lastModified).toGMTString());
+            t.sendResponseHeaders(200, length);
+            responseFileContent(t, f);
+            
+            if(s_logger.isInfoEnabled())
+                s_logger.info("Sent file " + path + " with content type " + contentType);
+        } else {
+            if(s_logger.isInfoEnabled())
+                s_logger.info("file does not exist" + path);
+            t.sendResponseHeaders(404, -1);
+        }
+    }
+    
+    private static String getContentType(String extension) {
+        String key = extension.toLowerCase();
+        if(s_mimeTypes.containsKey(key)) {
+            return s_mimeTypes.get(key);
+        }
+        return "application/octet-stream"; 
+    }
+    
+    private static void responseFileContent(HttpExchange t, File f) throws Exception {
+        OutputStream os = t.getResponseBody();
+        FileInputStream fis = new FileInputStream(f);
+        while (true) {
+            byte[] b = new byte[8192];
+            int n = fis.read(b);
+            if (n < 0) {
+                break;
+            }
+            os.write(b, 0, n);
+        }
+        fis.close();
+        os.close();
+    }
+    
+    private static boolean validatePath(String path) {
+        int i = path.indexOf("/");
+        if(i == -1) {
+            if(s_logger.isInfoEnabled())
+                s_logger.info("Invalid resource path: can not start at resource root");
+            return false;
+        }
+        
+        if(path.contains("..")) {
+            if(s_logger.isInfoEnabled())
+                s_logger.info("Invalid resource path: contains relative up-level navigation");
+            
+            return false;
+        }
+        
+        return isValidResourceFolder(path.substring(0, i));
+    }
+    
+    private static boolean isValidResourceFolder(String name) {
+        return s_validResourceFolders.containsKey(name); 
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java
new file mode 100644
index 0000000..ee0ee13
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java
@@ -0,0 +1,145 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.log4j.Logger;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import com.sun.net.httpserver.HttpsServer;
+
+public class ConsoleProxySecureServerFactoryImpl implements ConsoleProxyServerFactory  {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxySecureServerFactoryImpl.class);
+    
+    private SSLContext sslContext = null;
+    
+    public ConsoleProxySecureServerFactoryImpl() {
+    }
+    
+    @Override
+    public void init(byte[] ksBits, String ksPassword) {
+        s_logger.info("Start initializing SSL");
+
+        if(ksBits == null) {
+            try {
+                s_logger.info("Initializing SSL from built-in default certificate");
+                
+                char[] passphrase = "vmops.com".toCharArray();
+                KeyStore ks = KeyStore.getInstance("JKS");
+                
+                ks.load(new FileInputStream("certs/realhostip.keystore"), passphrase);
+                // ks.load(ConsoleProxy.class.getResourceAsStream("/realhostip.keystore"), passphrase);
+                
+                s_logger.info("SSL certificate loaded");
+                
+                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+                kmf.init(ks, passphrase);
+                s_logger.info("Key manager factory is initialized");
+
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+                tmf.init(ks);
+                s_logger.info("Trust manager factory is initialized");
+
+                sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+                s_logger.info("SSL context is initialized");
+            } catch (Exception ioe) {
+                s_logger.error(ioe.toString(), ioe);
+            }
+            
+        } else {
+            char[] passphrase = ksPassword != null ? ksPassword.toCharArray() : null;
+            try {
+                s_logger.info("Initializing SSL from passed-in certificate");
+                
+                KeyStore ks = KeyStore.getInstance("JKS");
+                ks.load(new ByteArrayInputStream(ksBits), passphrase);
+                
+                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+                kmf.init(ks, passphrase);
+                s_logger.info("Key manager factory is initialized");
+        
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+                tmf.init(ks);
+                s_logger.info("Trust manager factory is initialized");
+        
+                sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+                s_logger.info("SSL context is initialized");
+            } catch(Exception e) {
+                s_logger.error("Unable to init factory due to exception ", e);
+            }
+        }
+        
+    }
+    
+    public HttpServer createHttpServerInstance(int port) throws IOException {
+        try {
+            HttpsServer server = HttpsServer.create(new InetSocketAddress(port), 5);
+            server.setHttpsConfigurator (new HttpsConfigurator(sslContext) {
+                @Override
+                public void configure (HttpsParameters params) {
+
+                // get the remote address if needed
+                InetSocketAddress remote = params.getClientAddress();
+                SSLContext c = getSSLContext();
+
+                // get the default parameters
+                SSLParameters sslparams = c.getDefaultSSLParameters();
+
+                params.setSSLParameters(sslparams);
+                // statement above could throw IAE if any params invalid.
+                // eg. if app has a UI and parameters supplied by a user.
+                }
+            });
+            
+            s_logger.info("create HTTPS server instance on port: " + port);
+            return server;
+        } catch (Exception ioe) {
+            s_logger.error(ioe.toString(), ioe);
+        }
+        return null;
+    }
+    
+    public SSLServerSocket createSSLServerSocket(int port) throws IOException {
+        try {
+            SSLServerSocket srvSock = null;
+            SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
+            srvSock = (SSLServerSocket) ssf.createServerSocket(port);
+            
+            s_logger.info("create SSL server socket on port: " + port);
+            return srvSock;
+        } catch (Exception ioe) {
+            s_logger.error(ioe.toString(), ioe);
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java
new file mode 100644
index 0000000..7e0e5c7
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java
@@ -0,0 +1,29 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.IOException;
+
+import javax.net.ssl.SSLServerSocket;
+
+import com.sun.net.httpserver.HttpServer;
+
+public interface ConsoleProxyServerFactory {
+    void init(byte[] ksBits, String ksPassword);
+    HttpServer createHttpServerInstance(int port) throws IOException;
+    SSLServerSocket createSSLServerSocket(int port) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java
new file mode 100644
index 0000000..6d34d3b
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java
@@ -0,0 +1,212 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class ConsoleProxyThumbnailHandler implements HttpHandler {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyThumbnailHandler.class);
+    
+    public ConsoleProxyThumbnailHandler() {
+    }
+
+    public void handle(HttpExchange t) throws IOException {
+        try {
+            Thread.currentThread().setName("JPG Thread " + 
+                    Thread.currentThread().getId() + " " + t.getRemoteAddress());
+            
+            if(s_logger.isDebugEnabled())
+                s_logger.debug("ScreenHandler " + t.getRequestURI());
+            
+            long startTick = System.currentTimeMillis();
+            doHandle(t);
+            
+            if(s_logger.isDebugEnabled())
+                s_logger.debug(t.getRequestURI() + "Process time " + (System.currentTimeMillis() - startTick) + " ms");
+        } catch (IllegalArgumentException e) {
+            String response = "Bad query string";
+            s_logger.error(response + ", request URI : " + t.getRequestURI());
+            t.sendResponseHeaders(200, response.length());
+            OutputStream os = t.getResponseBody();
+            os.write(response.getBytes());
+            os.close();
+        } catch(OutOfMemoryError e) {
+            s_logger.error("Unrecoverable OutOfMemory Error, exit and let it be re-launched");
+            System.exit(1);
+        } catch (Throwable e) {
+            s_logger.error("Unexpected exception while handing thumbnail request, ", e);
+            
+            String queries = t.getRequestURI().getQuery();
+            Map<String, String> queryMap = getQueryMap(queries);
+            int width = 0;
+            int height = 0;
+            String ws = queryMap.get("w");
+            String hs = queryMap.get("h");
+            try {
+                width = Integer.parseInt(ws);
+                height = Integer.parseInt(hs);
+            } catch (NumberFormatException ex) {
+            }
+            width = Math.min(width, 800);
+            height = Math.min(height, 600);
+            
+            BufferedImage img = generateTextImage(width, height, "Cannot Connect");
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(8196);
+            javax.imageio.ImageIO.write(img, "jpg", bos);
+            byte[] bs = bos.toByteArray();
+            Headers hds = t.getResponseHeaders();
+            hds.set("Content-Type", "image/jpeg");
+            hds.set("Cache-Control", "no-cache");
+            hds.set("Cache-Control", "no-store");
+            t.sendResponseHeaders(200, bs.length);
+            OutputStream os = t.getResponseBody();
+            os.write(bs);
+            os.close();
+            s_logger.error("Cannot get console, sent error JPG response for " + t.getRequestURI());
+            return;
+        } finally {
+            t.close();
+        }
+    }
+    
+    private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException {
+        String queries = t.getRequestURI().getQuery();
+        Map<String, String> queryMap = getQueryMap(queries);
+        int width = 0;
+        int height = 0;
+        int port = 0;
+        String ws = queryMap.get("w");
+        String hs = queryMap.get("h");
+        String host = queryMap.get("host");
+        String portStr = queryMap.get("port");
+        String sid = queryMap.get("sid");
+        String tag = queryMap.get("tag");
+        String ticket = queryMap.get("ticket");
+        String console_url = queryMap.get("consoleurl");
+        String console_host_session = queryMap.get("sessionref");
+
+        if(tag == null)
+            tag = "";
+        
+        if (ws == null || hs == null || host == null || portStr == null || sid == null ) {
+            throw new IllegalArgumentException();
+        }
+        try {
+            width = Integer.parseInt(ws);
+            height = Integer.parseInt(hs);
+            port = Integer.parseInt(portStr);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        ConsoleProxyClientParam param = new ConsoleProxyClientParam();
+        param.setClientHostAddress(host);
+        param.setClientHostPort(port);
+        param.setClientHostPassword(sid);
+        param.setClientTag(tag);
+        param.setTicket(ticket);
+        param.setClientTunnelUrl(console_url);
+        param.setClientTunnelSession(console_host_session);
+        
+        ConsoleProxyClient viewer = ConsoleProxy.getVncViewer(param);
+        
+        if (!viewer.isHostConnected()) {
+            // use generated image instead of static
+            BufferedImage img = generateTextImage(width, height, "Connecting");
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(8196);
+            javax.imageio.ImageIO.write(img, "jpg", bos);
+            byte[] bs = bos.toByteArray();
+            Headers hds = t.getResponseHeaders();
+            hds.set("Content-Type", "image/jpeg");
+            hds.set("Cache-Control", "no-cache");
+            hds.set("Cache-Control", "no-store");
+            t.sendResponseHeaders(200, bs.length);
+            OutputStream os = t.getResponseBody();
+            os.write(bs);
+            os.close();
+            
+            if(s_logger.isInfoEnabled())
+                s_logger.info("Console not ready, sent dummy JPG response");
+            return;
+        }
+        
+        {
+            Image scaledImage = viewer.getClientScaledImage(width, height);
+            BufferedImage bufferedImage = new BufferedImage(width, height,
+                    BufferedImage.TYPE_3BYTE_BGR);
+            Graphics2D bufImageGraphics = bufferedImage.createGraphics();
+            bufImageGraphics.drawImage(scaledImage, 0, 0, null);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(8196);
+            javax.imageio.ImageIO.write(bufferedImage, "jpg", bos);
+            byte[] bs = bos.toByteArray();
+            Headers hds = t.getResponseHeaders();
+            hds.set("Content-Type", "image/jpeg");
+            hds.set("Cache-Control", "no-cache");
+            hds.set("Cache-Control", "no-store");
+            t.sendResponseHeaders(200, bs.length);
+            OutputStream os = t.getResponseBody();
+            os.write(bs);
+            os.close();
+        }
+    }
+    
+    public static BufferedImage generateTextImage(int w, int h, String text) {
+        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
+        Graphics2D g = img.createGraphics();
+        g.setColor(Color.BLACK);
+        g.fillRect(0, 0, w, h);
+        g.setColor(Color.WHITE);
+        try {
+            g.setFont(new Font(null, Font.PLAIN, 12));
+            FontMetrics fm = g.getFontMetrics();
+            int textWidth = fm.stringWidth(text);
+            int startx = (w-textWidth) / 2;
+            if(startx < 0)
+                startx = 0;
+            g.drawString(text, startx, h/2);
+        } catch (Throwable e) {
+            s_logger.warn("Problem in generating text to thumnail image, return blank image");
+        }
+        return img;
+    }
+
+    public static Map<String, String> getQueryMap(String query) {
+        String[] params = query.split("&");
+        Map<String, String> map = new HashMap<String, String>();
+        for (String param : params) {
+            String name = param.split("=")[0];
+            String value = param.split("=")[1];
+            map.put(name, value);
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java
new file mode 100644
index 0000000..6a473b5
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java
@@ -0,0 +1,235 @@
+// 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.
+package com.cloud.consoleproxy;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.UnknownHostException;
+
+import org.apache.log4j.Logger;
+
+import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
+import com.cloud.consoleproxy.vnc.RfbConstants;
+import com.cloud.consoleproxy.vnc.VncClient;
+
+/**
+ * 
+ * ConsoleProxyVncClient bridges a VNC engine with the front-end AJAX viewer
+ * 
+ */
+public class ConsoleProxyVncClient extends ConsoleProxyClientBase {
+    private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class);
+
+    private static final int SHIFT_KEY_MASK = 64;
+    private static final int CTRL_KEY_MASK = 128;
+    private static final int META_KEY_MASK = 256;
+    private static final int ALT_KEY_MASK = 512;
+    
+    private static final int X11_KEY_SHIFT = 0xffe1;
+    private static final int X11_KEY_CTRL = 0xffe3;
+    private static final int X11_KEY_ALT = 0xffe9;
+    private static final int X11_KEY_META = 0xffe7;
+    
+    private VncClient client;
+    private Thread worker;
+    private boolean workerDone = false;
+    
+    private int lastModifierStates = 0;
+    private int lastPointerMask = 0;
+    
+    public ConsoleProxyVncClient() {
+    }
+    
+    public boolean isHostConnected() {
+        if(client != null)
+            return client.isHostConnected();
+        
+        return false;
+    }
+    
+    @Override
+    public boolean isFrontEndAlive() {
+        if(workerDone || System.currentTimeMillis() - getClientLastFrontEndActivityTime() > ConsoleProxy.VIEWER_LINGER_SECONDS*1000) {
+            s_logger.info("Front end has been idle for too long");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void initClient(ConsoleProxyClientParam param) {
+        setClientParam(param);
+        
+        client = new VncClient(this);
+        worker = new Thread(new Runnable() {
+            public void run() {
+                String tunnelUrl = getClientParam().getClientTunnelUrl();
+                String tunnelSession = getClientParam().getClientTunnelSession();
+                
+                for(int i = 0; i < 15; i++) {
+                    try {
+                        if(tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) {
+                            URI uri = new URI(tunnelUrl);
+                            s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: " + tunnelSession);
+                            
+                            ConsoleProxy.ensureRoute(uri.getHost());
+                            client.connectTo(
+                                uri.getHost(), uri.getPort(), 
+                                uri.getPath() + "?" + uri.getQuery(), 
+                                tunnelSession, "https".equalsIgnoreCase(uri.getScheme()),
+                                getClientHostPassword());
+                        } else {
+                            s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: " + getClientHostPort());
+                            ConsoleProxy.ensureRoute(getClientHostAddress());
+                            client.connectTo(getClientHostAddress(), getClientHostPort(), getClientHostPassword());
+                        }
+                    } catch (UnknownHostException e) {
+                        s_logger.error("Unexpected exception (will retry until timeout)", e);
+                    } catch (IOException e) {
+                        s_logger.error("Unexpected exception (will retry until timeout) ", e);
+                    } catch (Throwable e) {
+                        s_logger.error("Unexpected exception (will retry until timeout) ", e);
+                    }
+                    
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                    }
+
+                    if(tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null && !tunnelSession.isEmpty()) {
+                        ConsoleProxyAuthenticationResult authResult = ConsoleProxy.reAuthenticationExternally(getClientParam());
+                        if(authResult != null && authResult.isSuccess()) {
+                            if(authResult.getTunnelUrl() != null && !authResult.getTunnelUrl().isEmpty() && 
+                                authResult.getTunnelSession() != null && !authResult.getTunnelSession().isEmpty()) {
+                                tunnelUrl = authResult.getTunnelUrl();
+                                tunnelSession = authResult.getTunnelSession();
+                                
+                                s_logger.info("Reset XAPI session. url: " + tunnelUrl + ", session: " + tunnelSession);
+                            }
+                        }
+                    }
+                }
+
+                s_logger.info("Receiver thread stopped.");
+                workerDone = true;
+                client.getClientListener().onClientClose();
+            }
+        });
+        
+        worker.setDaemon(true);
+        worker.start();
+    }
+    
+    @Override
+    public void closeClient() {
+        if(client != null)
+            client.shutdown();
+    }
+    
+    @Override
+    public void onClientConnected() {
+    }
+    
+    public void onClientClose() {
+        s_logger.info("Received client close indication. remove viewer from map.");
+        
+        ConsoleProxy.removeViewer(this);
+    }
+    
+    @Override
+    public void onFramebufferUpdate(int x, int y, int w, int h) {
+        super.onFramebufferUpdate(x, y, w, h);
+        client.requestUpdate(false);
+    }
+
+    public void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers) {
+        if(client == null)
+            return;
+        
+        updateFrontEndActivityTime();
+        
+        switch(event) {
+        case KEY_DOWN :
+            sendModifierEvents(modifiers);
+            client.sendClientKeyboardEvent(RfbConstants.KEY_DOWN, code, 0);
+            break;
+            
+        case KEY_UP :
+            client.sendClientKeyboardEvent(RfbConstants.KEY_UP, code, 0);
+            sendModifierEvents(0);
+            break;
+            
+        case KEY_PRESS :
+            break;
+            
+        default :
+            assert(false);
+            break;
+        }
+    }
+    
+    public void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers) {
+        if(client == null)
+            return;
+        
+        updateFrontEndActivityTime();
+
+        if (event == InputEventType.MOUSE_DOWN) {
+            if (code == 2) {
+                lastPointerMask |= 4;
+            } else if (code == 0) {
+                lastPointerMask |= 1;
+            }
+        }
+        
+        if (event == InputEventType.MOUSE_UP) {
+            if (code == 2) {
+                lastPointerMask ^= 4;
+            } else if (code == 0) {
+                lastPointerMask ^= 1;
+            }
+        }
+            
+        sendModifierEvents(modifiers);
+        client.sendClientMouseEvent(lastPointerMask, x, y, code, modifiers);
+        if(lastPointerMask == 0)
+            sendModifierEvents(0);
+    }
+    
+    @Override
+    protected FrameBufferCanvas getFrameBufferCavas() {
+        if(client != null)
+            return client.getFrameBufferCanvas();
+        return null;
+    }
+    
+    private void sendModifierEvents(int modifiers) {
+        if((modifiers & SHIFT_KEY_MASK) != (lastModifierStates & SHIFT_KEY_MASK))
+            client.sendClientKeyboardEvent((modifiers & SHIFT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_SHIFT, 0);
+            
+        if((modifiers & CTRL_KEY_MASK) != (lastModifierStates & CTRL_KEY_MASK))
+            client.sendClientKeyboardEvent((modifiers & CTRL_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_CTRL, 0);
+
+        if((modifiers & META_KEY_MASK) != (lastModifierStates & META_KEY_MASK))
+            client.sendClientKeyboardEvent((modifiers & META_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_META, 0);
+        
+        if((modifiers & ALT_KEY_MASK) != (lastModifierStates & ALT_KEY_MASK))
+            client.sendClientKeyboardEvent((modifiers & ALT_KEY_MASK) != 0 ? RfbConstants.KEY_DOWN : RfbConstants.KEY_UP, X11_KEY_ALT, 0);
+        
+        lastModifierStates = modifiers;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java b/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java
new file mode 100644
index 0000000..4a5aff1
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/InputEventType.java
@@ -0,0 +1,58 @@
+// 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.
+package com.cloud.consoleproxy;
+
+public enum InputEventType {
+    MOUSE_MOVE(1),
+    MOUSE_DOWN(2),
+    MOUSE_UP(3),
+    KEY_PRESS(4),
+    KEY_DOWN(5),
+    KEY_UP(6),
+    MOUSE_DBLCLICK(8);
+    
+    int eventCode;
+    private InputEventType(int eventCode) {
+        this.eventCode = eventCode;
+    }
+    
+    public int getEventCode() { 
+        return eventCode; 
+    }
+    
+    public static InputEventType fromEventCode(int eventCode) {
+        switch(eventCode) {
+        case 1 :
+            return MOUSE_MOVE;
+        case 2 :
+            return MOUSE_DOWN;
+        case 3 :
+            return MOUSE_UP;
+        case 4 :
+            return KEY_PRESS;
+        case 5 :
+            return KEY_DOWN;
+        case 6 :
+            return KEY_UP;
+        case 8 :
+            return MOUSE_DBLCLICK;
+        default :
+            break;
+        }
+        throw new IllegalArgumentException("Unsupport event code: " + eventCode);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java
new file mode 100644
index 0000000..2ff82d7
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ITileScanListener.java
@@ -0,0 +1,25 @@
+// 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.
+package com.cloud.consoleproxy.util;
+
+import java.awt.Rectangle;
+import java.util.List;
+
+public interface ITileScanListener {
+    boolean onTileChange(Rectangle rowMergedRect, int row, int col);
+    void onRegionChange(List<Region> regionList);
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java
new file mode 100644
index 0000000..bb7373e
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/ImageHelper.java
@@ -0,0 +1,32 @@
+// 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.
+package com.cloud.consoleproxy.util;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class ImageHelper {
+    public static byte[] jpegFromImage(BufferedImage image) throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(128000);
+        javax.imageio.ImageIO.write(image, "jpg", bos);
+        
+        byte[] jpegBits = bos.toByteArray();
+        bos.close();
+        return jpegBits;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java
new file mode 100644
index 0000000..f4357bd
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/Logger.java
@@ -0,0 +1,223 @@
+// 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.
+package com.cloud.consoleproxy.util;
+
+// logger facility for dynamic switch between console logger used in Applet and log4j based logger
+public class Logger {
+    private static LoggerFactory factory = null;
+    
+    public static final int LEVEL_TRACE = 1;
+    public static final int LEVEL_DEBUG = 2;
+    public static final int LEVEL_INFO = 3;
+    public static final int LEVEL_WARN = 4;
+    public static final int LEVEL_ERROR = 5;
+    
+    private Class<?> clazz;
+    private Logger logger;
+    
+    private static int level = LEVEL_INFO;
+    
+    public static Logger getLogger(Class<?> clazz) {
+        return new Logger(clazz);
+    }
+    
+    public static void setFactory(LoggerFactory f) {
+        factory = f;
+    }
+    
+    public static void setLevel(int l) {
+        level = l;
+    }
+    
+    public Logger(Class<?> clazz) {
+        this.clazz = clazz;
+    }
+    
+    protected Logger() {
+    }
+    
+    public boolean isTraceEnabled() {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            return logger.isTraceEnabled();
+        }
+        return level <= LEVEL_TRACE;
+    }
+    
+    public boolean isDebugEnabled() {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            return logger.isDebugEnabled();
+        }
+        return level <= LEVEL_DEBUG;
+    }
+    
+    public boolean isInfoEnabled() {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            return logger.isInfoEnabled();
+        }
+        return level <= LEVEL_INFO;
+    }
+
+    public void trace(Object message) {
+        
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.trace(message);
+        } else {
+            if(level <= LEVEL_TRACE)
+                System.out.println(message);
+        }
+    }
+    
+    public void trace(Object message, Throwable exception) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.trace(message, exception);
+        } else {
+            if(level <= LEVEL_TRACE) {
+                System.out.println(message);
+                if (exception != null) {
+                    exception.printStackTrace(System.out);
+                }
+            }
+        }
+    }
+    
+    public void info(Object message) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.info(message);
+        } else {
+            if(level <= LEVEL_INFO)
+                System.out.println(message);
+        }
+    }
+    
+    public void info(Object message, Throwable exception) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.info(message, exception);
+        } else {        
+            if(level <= LEVEL_INFO) {
+                System.out.println(message);
+                if (exception != null) {
+                    exception.printStackTrace(System.out);
+                }
+            }
+        }
+    }
+    
+    public void debug(Object message) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.debug(message);
+        } else {
+            if(level <= LEVEL_DEBUG)
+                System.out.println(message);
+        }
+    }
+    
+    public void debug(Object message, Throwable exception) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.debug(message, exception);
+        } else {
+            if(level <= LEVEL_DEBUG) {
+                System.out.println(message);
+                if (exception != null) {
+                    exception.printStackTrace(System.out);
+                }
+            }
+        }
+    }
+    
+    public void warn(Object message) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.warn(message);
+        } else {
+            if(level <= LEVEL_WARN)
+                System.out.println(message);
+        }
+    }
+    
+    public void warn(Object message, Throwable exception) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.warn(message, exception);
+        } else {
+            if(level <= LEVEL_WARN) {
+                System.out.println(message);
+                if (exception != null) {
+                    exception.printStackTrace(System.out);
+                }
+            }
+        }
+    }
+    
+    public void error(Object message) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.error(message);
+        } else {
+            if(level <= LEVEL_ERROR)
+                System.out.println(message);
+        }
+    }
+    
+    public void error(Object message, Throwable exception) {
+        if(factory != null) {
+            if(logger == null)
+                logger = factory.getLogger(clazz);
+            
+            logger.error(message, exception);
+        } else {
+            if(level <= LEVEL_ERROR) {
+                System.out.println(message);
+                if (exception != null) {
+                    exception.printStackTrace(System.out);
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/4869f0ca/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java
----------------------------------------------------------------------
diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java b/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java
new file mode 100644
index 0000000..121411a
--- /dev/null
+++ b/services/console-proxy/server/src/com/cloud/consoleproxy/util/LoggerFactory.java
@@ -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.
+package com.cloud.consoleproxy.util;
+
+public interface LoggerFactory {
+    Logger getLogger(Class<?> clazz);
+}