/* * * Copyright (C) 2007-2015 Licensed to the Comunes Association (CA) under * one or more contributor license agreements (see COPYRIGHT for details). * The CA licenses this file to you under the GNU Affero General Public * License version 3, (the "License"); you may not use this file except in * compliance with the License. This file is part of kune. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package cc.kune.embed.client.panels; import org.waveprotocol.box.webclient.client.atmosphere.AtmosphereConnectionImpl; import org.waveprotocol.wave.util.escapers.GwtWaverefEncoder; import cc.kune.common.client.log.Log; import cc.kune.common.client.notify.NotifyUser; import cc.kune.common.shared.i18n.I18n; import cc.kune.common.shared.i18n.I18nTranslationService; import cc.kune.common.shared.utils.UrlParam; import cc.kune.core.client.events.UserSignOutEvent; import cc.kune.core.client.events.UserSignOutEvent.UserSignOutHandler; import cc.kune.core.client.services.ClientFileDownloadUtils; import cc.kune.core.client.state.Session; import cc.kune.core.client.state.TokenMatcher; import cc.kune.core.client.state.impl.HistoryUtils; import cc.kune.core.shared.JSONConstants; import cc.kune.core.shared.dto.InitDataDTOJs; import cc.kune.core.shared.dto.StateAbstractDTO; import cc.kune.core.shared.dto.StateAbstractDTOJs; import cc.kune.core.shared.dto.StateContentDTO; import cc.kune.core.shared.dto.StateTokenJs; import cc.kune.core.shared.dto.UserInfoDTOJs; import cc.kune.embed.client.EmbedHelper; import cc.kune.embed.client.conf.EmbedConfiguration; import cc.kune.embed.client.events.EmbAppStartEvent; import cc.kune.embed.client.events.EmbedOpenEvent; import cc.kune.gspace.client.viewers.WaveViewer; import cc.kune.wave.client.KuneWaveProfileManager; import cc.kune.wave.client.kspecific.WaveClientManager; import cc.kune.wave.client.kspecific.WaveClientProvider; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsonUtils; import com.google.gwt.core.client.RunAsyncCallback; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.http.client.Response; import com.google.gwt.http.client.URL; import com.google.gwt.user.client.History; import com.google.gwt.user.client.ui.RootPanel; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; /** * The Class EmbedPresenter is used to embed kune waves in other CMSs (for * instance) * * @author vjrj@ourproject.org (Vicente J. Ruiz Jurado) */ @Singleton public class EmbedPresenter implements ValueChangeHandler<String> { /** * The Interface EmbedView. */ public interface EmbedView extends WaveViewer { } private final ClientFileDownloadUtils clientDownUtils; private final boolean devMode = true; private final Session session; private final Provider<EmbedSitebar> sitebar; protected String stateTokenToOpen; private final Provider<EmbedView> view; /** * Instantiates a new embed presenter. * * @param eventBus * the event bus * @param view * the view * @param proxy * the proxy * @param siteService * the site service * @param service * the service * @param waveClientManager * the wave client manager * @param waveClient * the wave client * @param matcher * the matcher * @param session * the session */ @Inject public EmbedPresenter(final EventBus eventBus, final Provider<EmbedView> view, final WaveClientManager waveClientManager, final WaveClientProvider waveClient, final I18nTranslationService i18n, final Session session, final Provider<EmbedSitebar> sitebar, final ClientFileDownloadUtils clientDownUtils) { this.view = view; this.clientDownUtils = clientDownUtils; this.session = session; this.sitebar = sitebar; NotifyUser.showProgressLoading(); Log.info("Starting embed presenter"); // FIXME: Maybe use AppStart to detect browser compatibility in the future session.setEmbedded(true); TokenMatcher.init(GwtWaverefEncoder.INSTANCE); eventBus.addHandler(EmbedOpenEvent.getType(), new EmbedOpenEvent.EmbedOpenHandler() { @Override public void onEmbedOpen(final EmbedOpenEvent event) { stateTokenToOpen = event.getStateToken(); Log.info("Received EmbedOpenEvent with token: " + stateTokenToOpen); if (session.getInitData() == null) { // Not initialized Log.debug("Session data not ready"); } else { // ok, we can continue Log.debug("Session data ready"); getContentFromHistoryHash(stateTokenToOpen); } } }); eventBus.addHandler(EmbAppStartEvent.getType(), new EmbAppStartEvent.EmbAppStartHandler() { @Override public void onAppStart(final EmbAppStartEvent event) { // This event is generated after configuration via JSNI Log.debug("App start event after conf loaded"); onAppStarted(); } }); if (EmbedConfiguration.isReady()) { // The event was fired already, so start! Log.debug("App start after conf already loaded"); // We set the prefix for avatars url with the server url onAppStarted(); } else if (isCurrentHistoryHashValid(getCurrentHistoryHash())) { // The event was fired already, so start! Log.debug("App start after valid hash"); // We set the prefix for avatars url with the server url EmbedConfiguration.setDefValues(); onAppStarted(); } } private void getContentFromHistoryHash(final String stateTokenS) { Log.info("Opening statetoken: " + stateTokenS); final boolean isGroupToken = TokenMatcher.isGroupToken(stateTokenS); final boolean isWaveToken = TokenMatcher.isWaveToken(stateTokenS); final String suffix = isGroupToken ? "" : isWaveToken ? "ByWaveRef" : ""; if (isGroupToken || isWaveToken) { // Ok is a token like group.tool.number final String getContentUrl = EmbedHelper.getServerWithPath() + "cors/ContentCORSService/getContent" + suffix + "?" + new UrlParam(JSONConstants.TOKEN_PARAM, URL.encodeQueryString(stateTokenS)); // FIXME Exception if is not public? EmbedHelper.processRequest(getContentUrl, new Callback<Response, Void>() { @Override public void onFailure(final Void reason) { notFound(); } @Override public void onSuccess(final Response response) { // final StateToken currentToken = new StateToken(currentHash); NotifyUser.hideProgress(); final StateAbstractDTOJs state = JsonUtils.safeEval(response.getText()); final StateTokenJs stateTokenJs = (StateTokenJs) state.getStateToken(); // getContent returns the default content if doesn't finds the content if ((isGroupToken && stateTokenS.equals(stateTokenJs.getEncoded())) || (isWaveToken && stateTokenS.equals(state.getWaveRef()))) { onGetContentSuccessful(session, EmbedHelper.parse(state)); } else { // getContent returns def content if content not found notFound(); } } }); } else { // Do something notFound(); } } private String getCurrentHistoryHash() { return HistoryUtils.undoHashbang(History.getToken()); } private WaveViewer getView() { return view.get(); } private boolean isCurrentHistoryHashValid(final String currentHistoryHash) { return currentHistoryHash != null && (TokenMatcher.isGroupToken(currentHistoryHash) || TokenMatcher.isWaveToken(currentHistoryHash)); } /** * Not found. */ private void notFound() { NotifyUser.important(I18n.t("Content not found")); NotifyUser.hideProgress(); } private void onAppStarted() { // We set the prefix for avatars url with the server url final String serverUrl = EmbedConfiguration.get().getServerUrl(); clientDownUtils.setPrefix(serverUrl); final String serverNoSlash = serverUrl.replaceAll("/$", ""); KuneWaveProfileManager.urlPrefix = serverNoSlash; AtmosphereConnectionImpl.urlPrefix = serverNoSlash; final String userHash = session.getUserHash(); Log.info("Started embed presenter with user hash: " + userHash); final String initUrl = EmbedHelper.getServerWithPath() + "cors/SiteCORSService/getInitData"; EmbedHelper.processRequest(initUrl, new Callback<Response, Void>() { @Override public void onFailure(final Void reason) { // Do nothing Log.info("Failed to get init data"); } @Override public void onSuccess(final Response response) { final InitDataDTOJs initData = JsonUtils.safeEval(response.getText()); session.setInitData(EmbedHelper.parse(initData)); final UserInfoDTOJs userInfo = (UserInfoDTOJs) initData.getUserInfo(); if (userInfo != null) { session.setUserHash(userInfo.getUserHash()); session.setCurrentUserInfo(EmbedHelper.parse(userInfo), null); } else { if (session.getUserHash() != null) { // Probably the session expired session.signOut(); } } final String currentHash = getCurrentHistoryHash(); final boolean isValid = isCurrentHistoryHashValid(currentHash); if (stateTokenToOpen != null) { // The open event already received, so open the content Log.info("Opening token from JSNI open call"); getContentFromHistoryHash(stateTokenToOpen); } else if (isValid) { // We start the embed from the url #hash Log.info("Opening token from history token"); stateTokenToOpen = currentHash; getContentFromHistoryHash(currentHash); } else { // We embed the document via JSNI, so, we wait for the open event } // We configure sign-out session.onUserSignOut(false, new UserSignOutHandler() { @Override public void onUserSignOut(final UserSignOutEvent event) { Log.info("On user sign out"); if (stateTokenToOpen != null) { getContentFromHistoryHash(stateTokenToOpen); } } }); } }); } private void onGetContentSuccessful(final Session session, final StateAbstractDTO state) { getView().clear(); final StateContentDTO stateContent = (StateContentDTO) state; final boolean isLogged = session.isLogged(); final boolean isParticipant = stateContent.isParticipant(); Log.info("Is logged: " + isLogged + " isParticipant: " + isParticipant); final Boolean readOnly = EmbedConfiguration.get().getReadOnly(); Log.info("Is readonly: " + readOnly); final Boolean isReadOnly = readOnly == null ? false : readOnly; if (isLogged && isParticipant && !isReadOnly) { getView().setEditableContent(stateContent); } else { getView().setContent(stateContent); } // FIXME use GWTP here? GWT.runAsync(new RunAsyncCallback() { @Override public void onFailure(final Throwable reason) { // By now, do nothing } @Override public void onSuccess() { sitebar.get().init(stateTokenToOpen); } }); } @Override public void onValueChange(final ValueChangeEvent<String> event) { // Only used in dev mode if (devMode) { getContentFromHistoryHash(getCurrentHistoryHash()); } } public void show() { RootPanel.get("kune-embed-hook").add(getView()); } }