com.peergreen.webconsole.core.vaadin7.BaseUI.java Source code

Java tutorial

Introduction

Here is the source code for com.peergreen.webconsole.core.vaadin7.BaseUI.java

Source

/**
 * Peergreen S.A.S. All rights reserved.
 * Proprietary and confidential.
 *
 * 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.peergreen.webconsole.core.vaadin7;

import javax.security.auth.Subject;
import javax.servlet.http.Cookie;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.MissingHandlerException;
import org.apache.felix.ipojo.UnacceptableConfiguration;
import org.apache.felix.ipojo.annotations.Bind;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Unbind;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import com.peergreen.webconsole.Constants;
import com.peergreen.webconsole.core.context.BaseUIContext;
import com.peergreen.webconsole.core.extension.ExtensionFactory;
import com.peergreen.webconsole.core.extension.InstanceHandle;
import com.peergreen.webconsole.core.extension.InstanceState;
import com.peergreen.webconsole.core.navigator.BaseViewNavigator;
import com.peergreen.webconsole.core.notifier.InternalNotifierService;
import com.peergreen.webconsole.core.scope.Scope;
import com.peergreen.webconsole.core.scope.ScopeFactory;
import com.peergreen.webconsole.core.security.SecurityManager;
import com.peergreen.webconsole.navigator.NavigableModel;
import com.peergreen.webconsole.security.AuthenticateService;
import com.peergreen.webconsole.security.ISecurityManager;
import com.peergreen.webconsole.security.RoleGroup;
import com.peergreen.webconsole.security.RolePrincipal;
import com.peergreen.webconsole.security.UserPrincipal;
import com.vaadin.annotations.PreserveOnRefresh;
import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.data.Property;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutListener;
import com.vaadin.navigator.Navigator;
import com.vaadin.server.Page;
import com.vaadin.server.SessionDestroyEvent;
import com.vaadin.server.SessionDestroyListener;
import com.vaadin.server.ThemeResource;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.shared.ui.ui.Transport;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Image;
import com.vaadin.ui.Label;
import com.vaadin.ui.NativeButton;
import com.vaadin.ui.Notification;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.UIDetachedException;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.util.CurrentInstance;

/**
 * Base console UI
 *
 * @author Mohammed Boukada
 */
@Theme("dashboard")
@PreserveOnRefresh
@org.apache.felix.ipojo.annotations.Component
@Provides(specifications = UI.class)
@Push(transport = Transport.STREAMING)
public class BaseUI extends UI implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final String PEERGREEN_USER_COOKIE_NAME = "peergreen-user";

    private static final String ANONYMOUS_USER = "Anonymous";

    /**
     * Logger.
     */
    private static final Log LOGGER = LogFactory.getLog(BaseUI.class);

    /**
     * Max component width
     */
    private static final String MAX_WIDTH = "100%";

    /**
     * Max component height
     */
    private static final String MAX_HEIGHT = "100%";

    /**
     * Home scope name
     */
    public static final String HOME_SCOPE = "home";

    /**
     * Home scope alias
     */
    public static final String HOME_ALIAS = "/" + HOME_SCOPE;

    /**
     * Default scope icon class
     */
    public static final String DEFAULT_SCOPE_CLASSNAME = "icon-th-large";

    /**
     * Root layout
     */
    private CssLayout root = new CssLayout();

    /**
     * Progress indicator layout
     */
    private VerticalLayout progressIndicatorLayout;

    /**
     * Menu layout
     */
    private CssLayout menu;

    /**
     * Content layout
     */
    private CssLayout content = new CssLayout();

    /**
     * Notifications windows
     */
    private Window notifications;

    /**
     * Main content layout
     */
    private HorizontalLayout main;

    /**
     * Progress indicator
     */
    private ProgressBar progressIndicator = new ProgressBar(new Float(0.0));

    /**
     * Number of scopes to bind on bootstrap
     */
    private int nbScopesToBind = 0;

    /**
     * Scopes bound
     */
    private Map<ExtensionFactory, ScopeFactory> scopesFactories = new ConcurrentHashMap<>();

    /**
     * Scopes
     */
    private Map<String, Scope> scopes = new ConcurrentHashMap<>();

    /**
     * Console Id
     */
    private String consoleId;
    /**
     * Console name
     */
    private String consoleName;

    /**
     * Enable security
     */
    private Boolean enableSecurity;

    /**
     * Default roles for default user (development mode)
     */
    private List<String> defaultRoles;

    /**
     * Console domains
     */
    private List<String> domains;

    /**
     * Scope extension point label
     */
    private String scopeExtensionPoint;

    /**
     * Security manager
     */
    private ISecurityManager securityManager;

    /**
     * View navigator
     */
    private BaseViewNavigator viewNavigator;

    /**
     * UI id
     */
    private String uiId;

    /**
     * Notifier service
     */
    private InternalNotifierService notifierService;

    /**
     * Authentication service
     */
    private AuthenticateService authenticateService;

    public BaseUI() {
        // empty public no-args constructor
    }

    /**
     * Base console UI constructor
     */
    public BaseUI(String extensionPoint, String uiId, Boolean enableSecurity) {
        this.scopeExtensionPoint = extensionPoint;
        this.uiId = uiId;
        this.enableSecurity = enableSecurity;

        NavigableModel rootNavigableModel = new NavigableModel(null, "", null, null);
        this.viewNavigator = new BaseViewNavigator(new Navigator(this, content), rootNavigableModel);
    }

    public void setConsoleId(String consoleId) {
        this.consoleId = consoleId;
    }

    public void setConsoleName(String consoleName) {
        this.consoleName = consoleName;
    }

    public void setDefaultRoles(List<String> defaultRoles) {
        this.defaultRoles = defaultRoles;
    }

    public void setDomains(List<String> domains) {
        this.domains = domains;
    }

    @Bind
    public void bindNotifierService(InternalNotifierService notifierService) {
        this.notifierService = notifierService;
        viewNavigator.setNotifierService(notifierService);
    }

    @Unbind
    public void unbindNotifierService(InternalNotifierService notifierService) {
        clearComponentsForUi();
        this.notifierService = null;
    }

    @Bind
    public void bindAuthenticateService(AuthenticateService authenticateService) {
        this.authenticateService = authenticateService;
    }

    @Unbind
    public void unbindAuthenticateService(AuthenticateService authenticateService) {
        this.authenticateService = null;
    }

    @Invalidate
    public void stop() {
        clearComponentsForUi();
        for (Map.Entry<ExtensionFactory, ScopeFactory> scopeFactoryEntry : scopesFactories.entrySet()) {
            ScopeFactory scopeFactory = scopeFactoryEntry.getValue();
            if (scopeFactory.getInstance() != null) {
                scopeFactory.getInstance().stop();
                scopeFactory.setInstance(null);
            }
        }
    }

    private void clearComponentsForUi() {
        if (this.notifierService != null) {
            notifierService.clearComponentsForUI(this);
        }
    }

    /**
     * Bind a scope factory
     *
     * @param extensionFactory extension factory
     * @param props extension properties
     */
    @Bind(aggregate = true, optional = true)
    public void bindExtensionFactory(ExtensionFactory extensionFactory, Dictionary props) {
        if (canAddExtensionFactory(props)) {
            scopesFactories.remove(extensionFactory);
            String[] roles = (String[]) props.get(Constants.EXTENSION_ROLES);
            List<String> listRoles = (roles == null) ? new ArrayList<String>() : Arrays.asList(roles);
            ScopeFactory scopeFactory = new ScopeFactory(listRoles);
            if (progressIndicator.getValue() >= 1) {
                boolean failed = false;
                try {
                    InstanceHandle instance = extensionFactory
                            .create(new BaseUIContext(this, viewNavigator, securityManager, uiId, consoleId));
                    if (InstanceState.STOPPED.equals(instance.getState())) {
                        failed = true;
                    }
                    scopeFactory.setInstance(instance);
                } catch (MissingHandlerException | UnacceptableConfiguration | ConfigurationException e) {
                    LOGGER.error(e.getMessage(), e);
                    failed = true;
                }
                if (failed) {
                    String error = "Fail to add a scope for main UI. Please see logs";
                    if (notifierService != null) {
                        notifierService.addNotification(error);
                    }
                }
            }
            scopesFactories.put(extensionFactory, scopeFactory);
        }
    }

    /**
     * Unbind a scope factory
     *
     * @param extensionFactory extension factory
     */
    @Unbind
    public void unbindExtensionFactory(ExtensionFactory extensionFactory) {
        if (scopesFactories.containsKey(extensionFactory)) {
            if (scopesFactories.get(extensionFactory).getInstance() != null) {
                scopesFactories.get(extensionFactory).getInstance().stop();
            }
            scopesFactories.remove(extensionFactory);
        }
    }

    /**
     * Whether the extension factory is compatible with this UI console
     * @param props extension properties
     * @return true if the extension is compatible, false otherwise.
     */
    private boolean canAddExtensionFactory(Dictionary props) {
        // First, test if the extension point is the wanted one
        String extensionId = (String) props.get(Constants.EXTENSION_POINT);
        if (extensionId == null || !scopeExtensionPoint.equals(extensionId)) {
            return false;
        }

        // Test if extension roles allowed are compatible with the logged user
        String[] roles = (String[]) props.get(Constants.EXTENSION_ROLES);
        if (roles != null) {
            if (securityManager != null && !securityManager.isUserInRoles(Arrays.asList(roles))) {
                return false;
            }
        }

        // Test if the scope targets this console
        String[] domains = (String[]) props.get(Constants.CONSOLE_DOMAINS);
        if (domains != null) {
            for (String domain : domains) {
                if (this.domains.contains(domain)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Bind scope view. <br />
     * Filtering was done on {@link #bindExtensionFactory(com.peergreen.webconsole.core.extension.ExtensionFactory, java.util.Dictionary)},
     * this method is called for the authorized scopes.
     * @param scopeView scope view
     * @param props scope properties
     */
    @Bind(aggregate = true, optional = true)
    public void bindScopeView(Component scopeView, Dictionary props) {
        String scopeName = (String) props.get("scope.name");
        Object iconClass = props.get("scope.iconClass");
        String scopeIconClass = (iconClass == null || "".equals(iconClass)) ? DEFAULT_SCOPE_CLASSNAME
                : (String) props.get("scope.iconClass");
        String scopeAlias = (String) props.get(Constants.EXTENSION_ALIAS);
        Scope scope = new Scope(scopeName, scopeAlias, scopeIconClass, scopeView);
        scopes.put(scopeAlias, scope);
        viewNavigator.addRoute(scope);
        addScopeButtonInMenu(scope, progressIndicator.getValue() >= 1);
    }

    /**
     * Unbind scope view
     * @param scopeView scope view
     * @param props scope properties
     */
    @Unbind
    public void unbindScopeView(Component scopeView, Dictionary props) {
        String scopeAlias = (String) props.get(Constants.EXTENSION_ALIAS);
        viewNavigator.removeRoute(scopes.get(scopeAlias));
        removeScopeButtonInMenu(scopes.get(scopeAlias));
        scopes.remove(scopeAlias);
    }

    /**
     * Init UI
     *
     * @param request vaadin request
     */
    @Override
    protected void init(VaadinRequest request) {
        setLocale(Locale.US);
        getPage().setTitle("Welcome to " + consoleName);
        setContent(root);
        root.addStyleName("root");
        root.setSizeFull();

        Label bg = new Label();
        bg.setSizeUndefined();
        bg.addStyleName("login-bg");
        root.addComponent(bg);

        Boolean isLogged = (Boolean) getSession().getAttribute("is.logged");
        if (!enableSecurity || (isLogged != null && isLogged)) {
            securityManager = (ISecurityManager) getSession().getAttribute("security.manager");
            if (securityManager == null) {
                Subject defaultSubject = new Subject();
                defaultSubject.getPrincipals().add(new UserPrincipal(ANONYMOUS_USER));
                RoleGroup group = new RoleGroup();
                if (defaultRoles != null) {
                    for (String role : defaultRoles) {
                        group.addMember(new RolePrincipal(role));
                    }
                }
                defaultSubject.getPrincipals().add(group);
                defaultSubject.setReadOnly();
                securityManager = new SecurityManager(defaultSubject);
                getSession().setAttribute("security.manager", securityManager);
                getSession().getService().addSessionDestroyListener(new SessionDestroyListener() {
                    @Override
                    public void sessionDestroy(SessionDestroyEvent sessionDestroyEvent) {
                        sessionDestroyEvent.getSession().getSession().removeAttribute("security.manager");
                    }
                });
            }
            buildMainView();
        } else {
            //            Cookie userCookie = getCookieByName(PEERGREEN_USER_COOKIE_NAME);
            //            if (userCookie != null) {
            //                String token = userCookie.getValue();
            //                // get user by token and show main view
            //            }
            buildLoginView(false);
        }
    }

    /**
     * Build login view
     *
     * @param exit
     */
    private void buildLoginView(final boolean exit) {
        if (exit) {
            root.removeAllComponents();
        }
        notifierService.closeAll();

        addStyleName("login");

        VerticalLayout loginLayout = new VerticalLayout();
        loginLayout.setId("webconsole_loginlayout_id");
        loginLayout.setSizeFull();
        loginLayout.addStyleName("login-layout");
        root.addComponent(loginLayout);

        final CssLayout loginPanel = new CssLayout();
        loginPanel.addStyleName("login-panel");

        HorizontalLayout labels = new HorizontalLayout();
        labels.setWidth(MAX_WIDTH);
        labels.setMargin(true);
        loginPanel.addComponent(labels);

        Label welcome = new Label("Welcome");
        welcome.addStyleName("h4");
        labels.addComponent(welcome);
        labels.setComponentAlignment(welcome, Alignment.MIDDLE_LEFT);

        Label title = new Label(consoleName);
        //title.setSizeUndefined();
        title.addStyleName("h2");
        title.addStyleName("light");
        labels.addComponent(title);
        labels.setComponentAlignment(title, Alignment.MIDDLE_RIGHT);

        HorizontalLayout fields = new HorizontalLayout();
        fields.setSpacing(true);
        fields.setMargin(true);
        fields.addStyleName("fields");

        final TextField username = new TextField("Username");
        username.focus();
        username.setId("webconsole_login_username");
        fields.addComponent(username);

        final PasswordField password = new PasswordField("Password");
        password.setId("webconsole_login_password");
        fields.addComponent(password);

        final Button signin = new Button("Sign In");
        signin.setId("webconsole_login_signin");
        signin.addStyleName("default");
        fields.addComponent(signin);
        fields.setComponentAlignment(signin, Alignment.BOTTOM_LEFT);

        final ShortcutListener enter = new ShortcutListener("Sign In", ShortcutAction.KeyCode.ENTER, null) {
            @Override
            public void handleAction(Object sender, Object target) {
                signin.click();
            }
        };

        signin.addShortcutListener(enter);
        loginPanel.addComponent(fields);

        HorizontalLayout bottomRow = new HorizontalLayout();
        bottomRow.setWidth(MAX_WIDTH);
        bottomRow.setMargin(new MarginInfo(false, true, false, true));
        final CheckBox keepLoggedIn = new CheckBox("Keep me logged in");
        bottomRow.addComponent(keepLoggedIn);
        bottomRow.setComponentAlignment(keepLoggedIn, Alignment.MIDDLE_LEFT);
        // Add new error message
        final Label error = new Label("Wrong username or password.", ContentMode.HTML);
        error.setId("webconsole_login_error");
        error.addStyleName("error");
        error.setSizeUndefined();
        error.addStyleName("light");
        // Add animation
        error.addStyleName("v-animate-reveal");
        error.setVisible(false);
        bottomRow.addComponent(error);
        bottomRow.setComponentAlignment(error, Alignment.MIDDLE_RIGHT);
        loginPanel.addComponent(bottomRow);

        signin.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                if (authenticate(username.getValue(), password.getValue())) {
                    //                    if (keepLoggedIn.getValue()) {
                    //                        //Cookie userCookie = getCookieByName(PEERGREEN_USER_COOKIE_NAME);
                    //                       if (getCookieByName(PEERGREEN_USER_COOKIE_NAME) == null) {
                    //                            // Get a token for this user and create a cooki
                    //                            Page.getCurrent().getJavaScript().execute( String.format("document.cookie = '%s=%s; path=%s'",
                    //                                    PEERGREEN_USER_COOKIE_NAME, token, VaadinService.getCurrentRequest().getContextPath()));
                    //                        } else {
                    //                            // update token
                    //                            userCookie.setValue(token);
                    //                            userCookie.setPath(VaadinService.getCurrentRequest().getContextPath());
                    //                        }
                    //                    }

                    buildMainView();
                } else {
                    error.setVisible(true);
                }
            }
        });

        loginLayout.addComponent(loginPanel);
        loginLayout.setComponentAlignment(loginPanel, Alignment.MIDDLE_CENTER);
    }

    private Cookie getCookieByName(String name) {
        // Fetch all cookies from the request
        Cookie[] cookies = VaadinService.getCurrentRequest().getCookies();

        // Iterate to find cookie by its name
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie;
            }
        }

        return null;
    }

    /**
     * Authenticate a user
     * @param username user name
     * @param password user password
     * @return True if the user was authenticated, false otherwise.
     */
    private boolean authenticate(String username, String password) {
        if (authenticateService != null) {
            Subject subject = authenticateService.authenticate(username, password);
            if (subject != null) {
                securityManager = new SecurityManager(subject);
                getSession().setAttribute("is.logged", true);
                getSession().setAttribute("security.manager", securityManager);
                return true;
            }
        } else {
            Notification.show("There is no authentication service. Please review your configuration");
        }
        return false;
    }

    /**
     * Build main view
     */
    private void buildMainView() {
        getPage().setTitle(consoleName);
        menu = new CssLayout();
        menu.setId("webconsole_mainlayout_id");

        notifierService.closeAll();
        final Button notify = new Button("");
        final HorizontalLayout tasksBar = new HorizontalLayout();
        tasksBar.setMargin(true);

        // Build menu layout
        main = new HorizontalLayout() {
            {
                setSizeFull();
                addStyleName("main-view");

                addComponent(new SidebarView());
                VerticalLayout contentRoot = new ConsoleContentView(notify, tasksBar);
                addComponent(contentRoot);
                setExpandRatio(contentRoot, 1);
            }

        };
        notifications = new NotificationWindow();
        notifierService.addNotificationsButton(notify, notifications, this);
        notifierService.addTasksBar(tasksBar, this);

        menu.removeAllComponents();

        // Tell scopesFactories view factories to create views
        nbScopesToBind = scopesFactories.size();
        createScopeViews();

        // Start progress indicator
        root.removeAllComponents();
        progressIndicatorLayout = new VerticalLayout();
        progressIndicatorLayout.setSizeFull();
        progressIndicatorLayout.addStyleName("login-layout");
        root.addComponent(progressIndicatorLayout);
        buildProgressIndicatorView();

        menu.addStyleName("menu");
        menu.setHeight(MAX_HEIGHT);

        navigateToPageLocation();
    }

    /**
     * Create scopes views
     *
     */
    private void createScopeViews() {
        for (Map.Entry<ExtensionFactory, ScopeFactory> scopeFactoryEntry : scopesFactories.entrySet()) {
            ExtensionFactory extensionFactory = scopeFactoryEntry.getKey();
            ScopeFactory scopeFactory = scopeFactoryEntry.getValue();
            boolean failed = false;
            try {
                InstanceHandle instance = extensionFactory
                        .create(new BaseUIContext(this, viewNavigator, securityManager, uiId, consoleId));
                if (InstanceState.STOPPED.equals(instance.getState())) {
                    failed = true;
                }
                scopeFactory.setInstance(instance);
            } catch (MissingHandlerException | UnacceptableConfiguration | ConfigurationException e) {
                LOGGER.error(e.getMessage(), e);
                failed = true;
            }

            if (failed) {
                String error = "Fail to add a scope for main UI. Please see logs";
                if (notifierService != null) {
                    notifierService.addNotification(error);
                }
            }
        }
    }

    /**
     * Get page location and navigate to
     */
    private void navigateToPageLocation() {
        String f = Page.getCurrent().getUriFragment();
        if (f != null && f.startsWith("!")) {
            f = f.substring(1);
        }

        if (f == null) {
            viewNavigator.navigateTo("/");
        } else {
            viewNavigator.navigateTo(f);
        }
    }

    /**
     * Build Welcome progress indicator view
     */
    private void buildProgressIndicatorView() {

        final CssLayout progressPanel = new CssLayout();
        progressPanel.addStyleName("login-panel");

        HorizontalLayout labels = new HorizontalLayout();
        labels.setWidth(MAX_WIDTH);
        labels.setMargin(true);
        progressPanel.addComponent(labels);

        Label welcome = new Label("Welcome " + ((securityManager == null) ? "" : securityManager.getUserName()));
        welcome.addStyleName("h4");
        labels.addComponent(welcome);
        labels.setComponentAlignment(welcome, Alignment.MIDDLE_LEFT);

        Label title = new Label(consoleName);
        title.addStyleName("h2");
        title.addStyleName("light");
        labels.addComponent(title);
        labels.setComponentAlignment(title, Alignment.MIDDLE_RIGHT);

        Float scopesViewsBound = (float) scopes.size();
        final Float stopValue = new Float(1.0);

        if (scopesFactories.isEmpty()) {
            progressIndicator.setValue(stopValue);
        } else {
            progressIndicator.setValue(scopesViewsBound / nbScopesToBind);
        }

        if (stopValue.equals(progressIndicator.getValue())) {
            showMainContent();
        } else {
            // We are still waiting for scopes we have requested their creation, wait a while.
            TimeOutThread timeOutThread = new TimeOutThread();
            timeOutThread.start();
            progressIndicator.addValueChangeListener(new Property.ValueChangeListener() {
                @Override
                public void valueChange(Property.ValueChangeEvent event) {
                    if (stopValue.equals(event.getProperty().getValue())) {
                        showMainContent();
                    }
                }
            });
        }

        HorizontalLayout progressBarPanel = new HorizontalLayout();
        progressBarPanel.setWidth(MAX_WIDTH);
        progressBarPanel.setMargin(true);
        progressBarPanel.addComponent(progressIndicator);
        progressBarPanel.setComponentAlignment(progressIndicator, Alignment.MIDDLE_CENTER);
        progressPanel.addComponent(progressBarPanel);

        progressIndicatorLayout.addComponent(progressPanel);
        progressIndicatorLayout.setComponentAlignment(progressPanel, Alignment.MIDDLE_CENTER);
    }

    /**
     * Show main view
     */
    protected void showMainContent() {
        removeStyleName("login");
        root.removeAllComponents();
        root.addComponent(main);
    }

    /**
     * Format console title
     *
     * @param title title
     * @return format title
     */
    private String formatTitle(String title) {
        StringBuilder sb = new StringBuilder();
        sb.append("<center><span>");
        sb.append(title);
        sb.append("</span></center>");
        return sb.toString();
    }

    /**
     * Add scope button in menu
     *
     * @param scope scope
     * @param notify for notifierService to show badge
     */
    private void addScopeButtonInMenu(final Scope scope, boolean notify) {
        if (menu != null) {
            final Button b = new NativeButton(scope.getScopeName().toUpperCase());

            b.addStyleName(scope.getScopeIconClass());

            b.addClickListener(new Button.ClickListener() {
                @Override
                public void buttonClick(Button.ClickEvent event) {
                    //clearMenuSelection();
                    notifierService.removeBadge(scope.getScopeView());
                    viewNavigator.navigateTo(scope.getScopeAlias());
                }
            });

            sortButtonsInMenu(scope.getScopeAlias(), b);

            notifierService.addScopeButton(scope.getScopeView(), b, this, notify);

            scopes.get(scope.getScopeAlias()).setScopeMenuButton(b);

            if (nbScopesToBind > 0) {
                Float progressIndicatorValue = progressIndicator.getValue();
                progressIndicatorValue += (float) (1.0 / nbScopesToBind);
                progressIndicator.setValue(progressIndicatorValue);
            }
        }
    }

    /**
     * Remove scope button from menu
     *
     * @param scope scope
     */
    private void removeScopeButtonInMenu(final Scope scope) {
        if (scope.getScopeMenuButton() != null) {
            access(new Runnable() {
                @Override
                public void run() {
                    menu.removeComponent(scope.getScopeMenuButton());
                }
            });
            scope.setScopeMenuButton(null);
            notifierService.removeScopeButton(scope.getScopeView());
        }
    }

    /**
     * Sort scope buttons
     * @param scopeAlias scope alias
     * @param b scope button
     */
    private void sortButtonsInMenu(final String scopeAlias, final Button b) {
        final LinkedList<String> scopesNames = new LinkedList<>();
        for (Map.Entry<String, Scope> scopeEntry : scopes.entrySet()) {
            scopesNames.add(scopeEntry.getValue().getScopeAlias());
        }
        Collections.sort(scopesNames);
        if (scopesNames.contains(HOME_ALIAS)) {
            scopesNames.remove(HOME_ALIAS);
            scopesNames.addFirst(HOME_ALIAS);
        }

        final List<Button> buttonsToShift = new LinkedList<>();
        buttonsToShift.add(b);

        access(new Runnable() {
            @Override
            public void run() {
                for (String scope : scopesNames) {
                    if (HOME_ALIAS.equals(scope)) {
                        continue;
                    }

                    if (HOME_ALIAS.equals(scopeAlias) || scopeAlias.compareTo(scope) < 0) {
                        buttonsToShift.add(scopes.get(scope).getScopeMenuButton());
                        menu.removeComponent(scopes.get(scope).getScopeMenuButton());
                    }
                }

                for (Button button : buttonsToShift) {
                    menu.addComponent(button);
                }
            }
        });
    }

    @Override
    public void accessSynchronously(Runnable runnable) {
        Map<Class<?>, CurrentInstance> old = null;

        VaadinSession session = getSession();

        if (session == null) {
            throw new UIDetachedException();
        }

        // TODO PGWK-7 - hack to avoid exception when another session had lock
        //VaadinService.verifyNoOtherSessionLocked(session);

        session.lock();
        try {
            if (getSession() == null) {
                // UI was detached after fetching the session but before we
                // acquired the lock.
                throw new UIDetachedException();
            }
            old = CurrentInstance.setCurrent(this);
            runnable.run();
        } finally {
            session.unlock();
            if (old != null) {
                CurrentInstance.restoreInstances(old);
            }
        }
    }

    /**
     * Scopes binding time out
     * @author Mohammed Boukada
     */
    private class TimeOutThread extends Thread {

        private static final int TWO_SECONDS = 2000;

        @Override
        public void run() {
            try {
                sleep(TWO_SECONDS);
                if (progressIndicator.getValue() < 1) {
                    access(new Runnable() {
                        @Override
                        public void run() {
                            showMainContent();
                        }
                    });
                }
            } catch (InterruptedException e) {
                access(new Runnable() {
                    @Override
                    public void run() {
                        showMainContent();
                    }
                });
            }
        }
    }

    /**
     * Set notification window position according to mouse click
     * @param event button click event
     */
    private void setNotificationsWindowPosition(Button.ClickEvent event) {
        notifications.setPositionX(event.getClientX() - event.getRelativeX());
        notifications.setPositionY(event.getClientY() - event.getRelativeY());
    }

    /**
     * Side bar view
     * @author Mohammed Boukada
     */
    private class SidebarView extends VerticalLayout {

        public SidebarView() {
            addStyleName("sidebar");
            setWidth("115px");
            setHeight(MAX_HEIGHT);

            // Branding element
            addComponent(new CssLayout() {
                {
                    addStyleName("branding");
                    Label title = new Label(formatTitle(consoleName), ContentMode.HTML);
                    title.setSizeUndefined();
                    addComponent(title);
                }
            });

            // Main menu
            addComponent(menu);
            setExpandRatio(menu, 1);

            if (securityManager != null) {
                // User menu
                VerticalLayout menuView = new ScopeMenuView();
                addComponent(menuView);
            }
        }
    }

    /**
     * Scopes menu view
     * @author Mohammed Boukada
     */
    private final class ScopeMenuView extends VerticalLayout {

        private ScopeMenuView() {
            setSizeUndefined();
            addStyleName("user");
            Image profilePic = new Image(null, new ThemeResource("img/profile-pic.png"));
            profilePic.setWidth("34px");
            addComponent(profilePic);
            Label userName = new Label(securityManager.getUserName());
            userName.setSizeUndefined();
            addComponent(userName);

            if (!ANONYMOUS_USER.equals(securityManager.getUserName())) {
                Button settings = new NativeButton("Settings");
                settings.addStyleName("icon-cog");
                settings.setDescription("Settings");
                addComponent(settings);
                settings.addClickListener(new Button.ClickListener() {
                    @Override
                    public void buttonClick(Button.ClickEvent clickEvent) {
                        Notification.show("Not implemented yet");
                    }
                });

                Button exit = new NativeButton("Exit");
                exit.addStyleName("icon-cancel");
                exit.setDescription("Sign Out");
                addComponent(exit);
                exit.addClickListener(new Button.ClickListener() {
                    @Override
                    public void buttonClick(Button.ClickEvent event) {
                        ((com.peergreen.webconsole.core.security.SecurityManager) securityManager)
                                .setUserLogged(false);
                        for (Map.Entry<ExtensionFactory, ScopeFactory> scopeFactoryEntry : scopesFactories
                                .entrySet()) {
                            ScopeFactory scopeFactory = scopeFactoryEntry.getValue();
                            if (scopeFactory.getInstance() != null) {
                                scopeFactory.getInstance().stop();
                                scopeFactory.setInstance(null);
                            }
                        }
                        nbScopesToBind = 0;
                        progressIndicator.setValue((float) 0);
                        getSession().setAttribute("is.logged", false);
                        buildLoginView(true);
                    }
                });
            }
        }
    }

    /**
     * Console content view
     * @author Mohammed Boukada
     */
    private final class ConsoleContentView extends VerticalLayout {

        private ConsoleContentView(final Button notify, final HorizontalLayout tasksBar) {
            setSizeFull();
            HorizontalLayout toolbar = new HorizontalLayout();
            toolbar.setWidth(MAX_WIDTH);
            toolbar.setSpacing(true);
            toolbar.addStyleName("toolbar");

            toolbar.addComponent(tasksBar);
            toolbar.setComponentAlignment(tasksBar, Alignment.MIDDLE_LEFT);
            toolbar.setExpandRatio(tasksBar, 1);

            notify.setDescription("Notifications");
            notify.addStyleName("notifications");
            notify.addStyleName("icon-only");
            notify.addStyleName("icon-notification");
            notify.addStyleName("fontello");

            notify.addClickListener(new Button.ClickListener() {
                @Override
                public void buttonClick(Button.ClickEvent event) {
                    event.getButton().removeStyleName("unread");
                    event.getButton().setDescription("Notifications");
                    if (notifications != null && notifications.getUI() != null) {
                        notifications.close();
                    } else {
                        setNotificationsWindowPosition(event);
                        getUI().addWindow(notifications);
                        notifications.focus();
                    }
                }
            });
            toolbar.addComponent(notify);
            toolbar.setComponentAlignment(notify, Alignment.MIDDLE_LEFT);

            addComponent(toolbar);
            addComponent(content);
            content.setSizeFull();
            content.addStyleName("view-content");
            setExpandRatio(content, 1.5f);

            HorizontalLayout infoBar = new HorizontalLayout();
            infoBar.setWidth(MAX_WIDTH);
            infoBar.setHeight("34px");
            infoBar.setSpacing(true);
            infoBar.addStyleName("toolbar");

            Label poweredBy = new Label("Powered by Peergreen. This is a trial version.");
            infoBar.addComponent(poweredBy);
            infoBar.setComponentAlignment(poweredBy, Alignment.MIDDLE_LEFT);

            Image logo = new Image(null, new ThemeResource("img/logo-peergreen.png"));
            logo.setHeight("25px");
            infoBar.addComponent(logo);
            infoBar.setComponentAlignment(logo, Alignment.MIDDLE_RIGHT);

            addComponent(infoBar);
        }
    }

    /**
     * Notification window
     * @author Mohammed Boukada
     */
    private class NotificationWindow extends Window {

        public NotificationWindow() {
            setCaption("Notifications");
            setHeight("80%");
            setWidth("300px");
            addStyleName("notifications");
            setClosable(false);
            setResizable(false);
            setDraggable(false);
            setCloseShortcut(ShortcutAction.KeyCode.ESCAPE, null);
        }
    }
}