// @formatter:off
/**
* Copyright 2010, 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package cc.kune.wave.client;
import java.util.Date;
import java.util.Set;
import java.util.logging.Logger;
import org.waveprotocol.box.webclient.client.ClientIdGenerator;
import org.waveprotocol.box.webclient.client.HistoryProvider;
import org.waveprotocol.box.webclient.client.HistorySupport;
import org.waveprotocol.box.webclient.client.RemoteViewServiceMultiplexer;
import org.waveprotocol.box.webclient.client.Session;
import org.waveprotocol.box.webclient.client.SimpleWaveStore;
import org.waveprotocol.box.webclient.client.WaveWebSocketClient;
import org.waveprotocol.box.webclient.search.RemoteSearchService;
import org.waveprotocol.box.webclient.search.SearchPanelView;
import org.waveprotocol.box.webclient.search.SearchPresenter;
import org.waveprotocol.box.webclient.search.SimpleSearch;
import org.waveprotocol.box.webclient.search.WaveStore;
import org.waveprotocol.box.webclient.widget.loading.LoadingIndicator;
import org.waveprotocol.wave.client.account.ProfileManager;
import org.waveprotocol.wave.client.common.safehtml.SafeHtml;
import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder;
import org.waveprotocol.wave.client.common.util.AsyncHolder.Accessor;
import org.waveprotocol.wave.client.doodad.attachment.AttachmentManagerImpl;
import org.waveprotocol.wave.client.doodad.attachment.AttachmentManagerProvider;
import org.waveprotocol.wave.client.events.ClientEvents;
import org.waveprotocol.wave.client.events.Log;
import org.waveprotocol.wave.client.events.NetworkStatusEvent;
import org.waveprotocol.wave.client.events.NetworkStatusEventHandler;
import org.waveprotocol.wave.client.events.WaveCreationEvent;
import org.waveprotocol.wave.client.events.WaveCreationEventHandler;
import org.waveprotocol.wave.client.events.WaveSelectionEvent;
import org.waveprotocol.wave.client.events.WaveSelectionEventHandler;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ViewFactories;
import org.waveprotocol.wave.client.widget.common.ImplPanel;
import org.waveprotocol.wave.client.widget.popup.CenterPopupPositioner;
import org.waveprotocol.wave.client.widget.popup.PopupChrome;
import org.waveprotocol.wave.client.widget.popup.PopupChromeFactory;
import org.waveprotocol.wave.client.widget.popup.PopupFactory;
import org.waveprotocol.wave.client.widget.popup.UniversalPopup;
import org.waveprotocol.wave.model.id.IdGenerator;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.waveref.InvalidWaveRefException;
import org.waveprotocol.wave.model.waveref.WaveRef;
import org.waveprotocol.wave.util.escapers.GwtWaverefEncoder;
import cc.kune.common.client.notify.NotifyUser;
import cc.kune.common.client.ui.EditableLabel;
import cc.kune.common.shared.utils.SimpleResponseCallback;
import cc.kune.core.client.errors.DefaultException;
import cc.kune.core.client.events.StackErrorEvent;
import cc.kune.core.client.rpcservices.AsyncCallbackSimple;
import cc.kune.core.client.rpcservices.ContentServiceAsync;
import cc.kune.core.client.sitebar.spaces.Space;
import cc.kune.core.client.sitebar.spaces.SpaceConfEvent;
import cc.kune.core.client.sitebar.spaces.SpaceSelectEvent;
import cc.kune.core.client.state.StateManager;
import cc.kune.core.client.state.TokenMatcher;
import cc.kune.core.client.state.impl.HistoryUtils;
import cc.kune.core.shared.dto.StateAbstractDTO;
import cc.kune.core.shared.dto.StateContentDTO;
import cc.kune.initials.InitialsResources;
import cc.kune.polymer.client.PolymerUtils;
import cc.kune.wave.client.kspecific.AfterOpenWaveEvent;
import cc.kune.wave.client.kspecific.AurorisColorPicker;
import cc.kune.wave.client.kspecific.BeforeOpenWaveEvent;
import cc.kune.wave.client.kspecific.OnWaveClientStartEvent;
import cc.kune.wave.client.kspecific.WaveClientClearEvent;
import cc.kune.wave.client.kspecific.WaveClientUtils;
import cc.kune.wave.client.kspecific.WaveClientView;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.UIObject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
// TODO: Auto-generated Javadoc
/**
* Entry point classes define <code>onModuleLoad()</code>.
*
*/
@Singleton
public class WebClient extends Composite implements WaveClientView {
/**
* The Interface Binder.
* */
interface Binder extends UiBinder<FlowPanel, WebClient> {
}
/**
* An exception handler that reports exceptions using a <em>shiny banner</em>
* (an alert placed on the top of the screen). Once the stack trace is
* prepared, it is revealed in the banner via a link.
*
*/
public static class ErrorHandler implements UncaughtExceptionHandler {
/**
* Gets the stack trace async.
*
* @param t the t
* @param whenReady the when ready
* @return the stack trace async
*/
public static void getStackTraceAsync(final Throwable t, final Accessor<SafeHtml> whenReady) {
// TODO: Request stack-trace de-obfuscation. For now, just use the
// javascript stack trace.
//
// Use minimal services here, in order to avoid the chance that reporting
// the error produces more errors. In particular, do not use WIAB's
// scheduler to run this command.
// Also, this code could potentially be put behind a runAsync boundary, to
// save whatever dependencies it uses from the initial download.
new Timer() {
@Override
public void run() {
final SafeHtmlBuilder stack = new SafeHtmlBuilder();
Throwable error = t;
while (error != null) {
final String token = String.valueOf((new Date()).getTime());
stack.appendHtmlConstant("Token: " + token + "<br> ");
stack.appendEscaped(String.valueOf(error.getMessage())).appendHtmlConstant("<br>");
for (final StackTraceElement elt : error.getStackTrace()) {
stack.appendHtmlConstant(" ")
.appendEscaped(maybe(elt.getClassName(), "??")).appendHtmlConstant(".") //
.appendEscaped(maybe(elt.getMethodName(), "??")).appendHtmlConstant(" (") //
.appendEscaped(maybe(elt.getFileName(), "??")).appendHtmlConstant(":") //
.appendEscaped(maybe(elt.getLineNumber(), "??")).appendHtmlConstant(")") //
.appendHtmlConstant("<br>");
}
error = error.getCause();
if (error != null) {
stack.appendHtmlConstant("Caused by: ");
}
}
whenReady.use(stack.toSafeHtml());
}
}.schedule(1);
}
/**
* Install.
*/
public static void install() {
GWT.setUncaughtExceptionHandler(new ErrorHandler(GWT.getUncaughtExceptionHandler()));
}
/**
* Maybe.
*
* @param value the value
* @param otherwise the otherwise
* @return the string
*/
private static String maybe(final int value, final String otherwise) {
return value != -1 ? String.valueOf(value) : otherwise;
}
/**
* Maybe.
*
* @param value the value
* @param otherwise the otherwise
* @return the string
*/
private static String maybe(final String value, final String otherwise) {
return value != null ? value : otherwise;
}
/**
* Indicates whether an error has already been reported (at most one error
* is ever reported by this handler).
*/
private boolean hasFired;
/** Next handler in the handler chain. */
private final UncaughtExceptionHandler next;
/**
* Instantiates a new error handler.
*
* @param next the next
*/
private ErrorHandler(final UncaughtExceptionHandler next) {
this.next = next;
}
/* (non-Javadoc)
* @see com.google.gwt.core.client.GWT.UncaughtExceptionHandler#onUncaughtException(java.lang.Throwable)
*/
@Override
public void onUncaughtException(final Throwable e) {
if (!hasFired) {
hasFired = true;
// final ErrorIndicatorPresenter error =
// ErrorIndicatorPresenter.create(RootPanel.get("banner"));
getStackTraceAsync(e, new Accessor<SafeHtml>() {
@Override
public void use(final SafeHtml stack) {
// error.addDetail(stack, null);
// REMOTE_LOG.severe(stack.asString().replace("<br>", "\n"));
final String message = stack.asString().replace("<br>", "\n");
NotifyUser.logError(message);
NotifyUser.showProgress("Error");
REMOTE_LOG.severe(message);
new Timer() {
@Override
public void run() {
NotifyUser.hideProgress();
}}.schedule(5000);
}
});
}
if (next != null) {
next.onUncaughtException(e);
}
}
}
/**
* The Interface Style.
*
* @author vjrj@ourproject.org (Vicente J. Ruiz Jurado)
*/
interface Style extends CssResource {
}
/** The Constant BINDER. */
private static final Binder BINDER = GWT.create(Binder.class);
/** The log. */
static Log LOG = Log.get(WebClient.class);
// Use of GWT logging is only intended for sending exception reports to the
// server, nothing else in the client should use java.util.logging.
// Please also see WebClientDemo.gwt.xml.
/** The Constant REMOTE_LOG. */
private static final Logger REMOTE_LOG = Logger.getLogger("REMOTE_LOG");
/**
* Creates a popup that warns about network disconnects.
*
* @return the universal popup
*/
private static UniversalPopup createTurbulencePopup() {
final PopupChrome chrome = PopupChromeFactory.createPopupChrome();
final UniversalPopup popup =
PopupFactory.createPopup(null, new CenterPopupPositioner(), chrome, true);
popup.add(new HTML("<div style='color: red; padding: 5px; text-align: center;'>"
+ "<b>A turbulence detected!<br></br>"
+ " Please save your last changes to somewhere and reload the wave.</b></div>"));
return popup;
}
/** The channel. */
private RemoteViewServiceMultiplexer channel;
/** The color picker. */
private Provider<AurorisColorPicker> colorPicker;
/** The content service. */
private final ContentServiceAsync contentService;
/** The event bus. */
private final EventBus eventBus;
/** The id generator. */
private IdGenerator idGenerator;
/** The kune session. */
private final cc.kune.core.client.state.Session kuneSession;
/** The loading. */
private final Element loading = new LoadingIndicator().getElement();
/** The logged in user. */
private ParticipantId loggedInUser;
/** The profiles. */
private final ProfileManager profiles;
private SimpleSearch search;
final SearchPanelView searchPanel;
/** The style. */
@UiField
Style style;
/** The turbulence popup. */
private final UniversalPopup turbulencePopup = createTurbulencePopup();
/** The wave panel, if a wave is open. */
private CustomStagesProvider wave;
/** The wave frame. */
@UiField
EditableLabel title;
@UiField
FlowPanel flow;
@UiField
ImplPanel waveHolder;
/** The wave store. */
private final WaveStore waveStore = new SimpleWaveStore();
/** The wave unsaved indicator. */
private final CustomSavedStateIndicator waveUnsavedIndicator;
/**
* Create a remote websocket to talk to the server-side FedOne service.
*/
private WaveWebSocketClient websocket;
private final CustomEditToolbar customEditToolbar;
private String currentOpenedWaveUri;
private final StateManager stateManager;
/**
* This is the entry point method.
*
* @param eventBus the event bus
* @param profiles the profiles
* @param tokenMatcher the token matcher
* @param kuneSession the kune session
* @param waveUnsavedIndicator the wave unsaved indicator
* @param contentService the content service
* @param colorPicker the color picker
*/
@Inject
public WebClient(final EventBus eventBus, final KuneWaveProfileManager profiles,
final cc.kune.core.client.state.Session kuneSession, StateManager stateManager,
final CustomSavedStateIndicator waveUnsavedIndicator, final ContentServiceAsync contentService,
final Provider<AurorisColorPicker> colorPicker, SearchPanelView searchPanel,
CustomEditToolbar customEditToolbar) {
this.eventBus = eventBus;
this.profiles = profiles;
this.kuneSession = kuneSession;
this.stateManager = stateManager;
this.waveUnsavedIndicator = waveUnsavedIndicator;
this.contentService = contentService;
this.colorPicker = colorPicker;
this.customEditToolbar = customEditToolbar;
this.searchPanel = searchPanel;
ErrorHandler.install();
eventBus.addHandler(StackErrorEvent.getType(), new StackErrorEvent.StackErrorHandler() {
@Override
public void onStackError(final StackErrorEvent event) {
getStackTraceAsync(event.getException(), new Accessor<SafeHtml>() {
@Override
public void use(final SafeHtml stack) {
final String stackAsString = stack.asString().replace("<br>", "\n");
NotifyUser.logError(stackAsString);
cc.kune.common.client.log.Log.error("Stack: " + stackAsString);
}
});
}
});
ClientEvents.get().addWaveCreationEventHandler(
new WaveCreationEventHandler() {
@Override
public void onCreateRequest(final WaveCreationEvent event, final Set<ParticipantId> participantSet) {
LOG.info("WaveCreationEvent received");
if (channel == null) {
throw new DefaultException("Spaghetti attack. Create occured before login");
}
openWave(WaveRef.of(idGenerator.newWaveId()), true, participantSet);
}
});
//setupConnectionIndicator();
// Done in StateManager
HistorySupport.init(new HistoryProvider() {
@Override
public String getToken() {
final String currentToken = History.getToken();
String waveToken = currentToken;
// FIXME what about preview?
if (TokenMatcher.isGroupToken(currentToken) || TokenMatcher.isHomeToken(currentToken)) {
waveToken = kuneSession.getContentState().getWaveRef();
LOG.info("Kune URL: " + currentToken + " = " + waveToken);
}
return waveToken;
}
});
loginImpl();
setupUi();
//WaveClientUtils.addListener(stateManager, wave, waveHolder, waveFrame);
if (TokenMatcher.isWaveToken(History.getToken())) {
History.fireCurrentHistoryState();
}
LOG.info("SimpleWebClient.onModuleLoad() done");
eventBus.fireEvent(new OnWaveClientStartEvent(this));
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#clear()
*/
@Override
public void clear() {
WaveClientUtils.clear(wave);
}
/**
* Creates the web socket.
*/
private void createWebSocket() {
websocket = new WaveWebSocketClient(webSocketNotSupported() || kuneSession.isEmbedded(), getWebSocketBaseUrl());
websocket.connect();
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#getChannel()
*/
@Override
public RemoteViewServiceMultiplexer getChannel() {
return channel;
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#getLoading()
*/
@Override
public Element getLoading() {
return loading;
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#getProfiles()
*/
@Override
public ProfileManager getProfiles() {
return profiles;
}
@Override
public SimpleSearch getSearch() {
return search;
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#getStackTraceAsync(java.lang.Throwable, org.waveprotocol.wave.client.common.util.AsyncHolder.Accessor)
*/
@Override
public void getStackTraceAsync(final Throwable caught, final Accessor<SafeHtml> accessor) {
ErrorHandler.getStackTraceAsync(caught, accessor);
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#getWebSocket()
*/
@Override
public WaveWebSocketClient getWebSocket() {
return websocket;
}
/**
* Returns <code>ws(s)://yourhost[:port]/</code>.
*
* @return the web socket base url
*/
// XXX check formatting wrt GPE
private native String getWebSocketBaseUrl() /*-{
return ((window.location.protocol == "https:") ? "wss" : "ws") + "://"
+ $wnd.__websocket_address + "/";
}-*/;
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#login()
*/
@Override
public void login() {
loginImpl();
}
/**
* Login impl.
*/
private void loginImpl() {
createWebSocket();
if (Session.get().isLoggedIn()) {
loggedInUser = new ParticipantId(Session.get().getAddress());
idGenerator = ClientIdGenerator.create();
loginToServer();
}
}
/**
* Login to server.
*/
private void loginToServer() {
assert loggedInUser != null;
channel = new RemoteViewServiceMultiplexer(websocket, loggedInUser.getAddress());
}
/* (non-Javadoc)
* @see cc.kune.wave.client.kspecific.WaveClientView#logout()
*/
@Override
public void logout() {
loggedInUser = null;
channel = null;
idGenerator = null;
websocket = null;
searchPanel.clearDigests();
clear();
}
/**
* Open wave.
*
* @param waveRef the wave ref
* @param isNewWave the is new wave
* @param participants the participants
*/
private void openWave(final WaveRef waveRef, final boolean isNewWave, final Set<ParticipantId> participants) {
waveUnsavedIndicator.onNewHistory(History.getToken(), new SimpleResponseCallback () {
@Override
public void onCancel() {
// Do nothing
}
@Override
public void onSuccess() {
openWaveImpl(waveRef, isNewWave, participants);
}});
}
/**
* Shows a wave in a wave panel.
*
* @param waveRef wave id to open
* @param isNewWave whether the wave is being created by this client session.
* @param participants the participants to add to the newly created wave.
* {@code null} if only the creator should be added
*/
private void openWaveImpl(final WaveRef waveRef, final boolean isNewWave, final Set<ParticipantId> participants) {
LOG.info("WebClient.openWave()");
NotifyUser.showProgress();
final String waveUri = GwtWaverefEncoder.encodeToUriPathSegment(waveRef);
contentService.getContentByWaveRef(kuneSession.getUserHash(), waveUri, new AsyncCallbackSimple<StateAbstractDTO>() {
@Override
public void onSuccess(StateAbstractDTO result) {
// It's a group content
if (result instanceof StateContentDTO) {
final StateContentDTO state = (StateContentDTO) result;
stateManager.setRetrievedStateAndGo(state);
SpaceSelectEvent.fire(eventBus, Space.groupSpace);
// If narrow, show
// PolymerUtils.hideInboxWithDelay();
} else {
// PolymerUtils.hideInboxCancel();;
// Not a group content
SpaceSelectEvent.fire(eventBus, Space.userSpace);
if (currentOpenedWaveUri.equals(waveUri)) {
// Trying to open twice, skip for the first time...
cc.kune.common.client.log.Log.info("Trying to open the same wave twice");
currentOpenedWaveUri = null;
return;
} else {
currentOpenedWaveUri = waveUri;
}
WaveClientClearEvent.fire(eventBus);
clear();
eventBus.fireEvent(new BeforeOpenWaveEvent(waveUri));;
// Release the display:none.
UIObject.setVisible(flow.getElement(), true);
waveHolder.getElement().appendChild(loading);
final Element holder = waveHolder.getElement().appendChild(Document.get().createDivElement());
final CustomStagesProvider wave = new CustomStagesProvider(
holder, waveUnsavedIndicator, waveHolder, title, waveRef, channel, idGenerator, profiles, waveStore, isNewWave, Session.get().getDomain(), participants, eventBus, colorPicker,customEditToolbar, ViewFactories.FIXED);
WebClient.this.wave = wave;
wave.load(new Command() {
@Override
public void execute() {
loading.removeFromParent();
NotifyUser.hideProgress();
}
});
eventBus.fireEvent(new AfterOpenWaveEvent(waveUri));;
PolymerUtils.setMainSelected();
if (PolymerUtils.isXSmall() || PolymerUtils.isMainDrawerNarrow())
PolymerUtils.setNarrowVisible(false);
// We can now open again the same wave without errors
currentOpenedWaveUri = null;
final String encodedToken = HistoryUtils.undoHashbang(History.getToken());
if (encodedToken != null && !encodedToken.isEmpty() &&
!TokenMatcher.isInboxToken(encodedToken) && !TokenMatcher.isGroupToken(encodedToken)) {
WaveRef fromWaveRef;
try {
fromWaveRef = GwtWaverefEncoder.decodeWaveRefFromPath(encodedToken);
} catch (final InvalidWaveRefException e) {
cc.kune.common.client.log.Log.info("History token contains invalid path: " + encodedToken);
return;
}
if (fromWaveRef.getWaveId().equals(waveRef.getWaveId())) {
// History change was caused by clicking on a link, it's already
// updated by browser.
cc.kune.common.client.log.Log.info("History change by clicking a link, should be already in the browser bar");
SpaceConfEvent.fire(eventBus, Space.userSpace, encodedToken);
return;
}
}
final String tokenFromWaveref = HistoryUtils.hashbang(waveUri);
cc.kune.common.client.log.Log.info("Token from opened wave: " + tokenFromWaveref);
SpaceConfEvent.fire(eventBus, Space.userSpace, tokenFromWaveref);
History.newItem(tokenFromWaveref, false);
}
}
});
}
/**
* Setup connection indicator.
*/
private void setupConnectionIndicator() {
ClientEvents.get().addNetworkStatusEventHandler(new NetworkStatusEventHandler() {
boolean isTurbulenceDetected = false;
@Override
public void onNetworkStatus(final NetworkStatusEvent event) {
final Element element = Document.get().getElementById("netstatus");
if (element != null) {
switch (event.getStatus()) {
case CONNECTED:
case RECONNECTED:
element.setInnerText("Online");
element.setClassName("online");
isTurbulenceDetected = false;
turbulencePopup.hide();
break;
case DISCONNECTED:
element.setInnerText("Offline");
element.setClassName("offline");
if (!isTurbulenceDetected) {
isTurbulenceDetected = true;
turbulencePopup.show();
}
break;
case NEVER_CONNECTED:
case RECONNECTING:
element.setInnerText("Connecting...");
element.setClassName("connecting");
break;
}
}
}
});
}
/**
* Setup search panel.
*/
private void setupSearchPanel() {
if (kuneSession.isEmbedded()) {
// We don't use search in embed system
return;
}
// On wave action fire an event.
final SearchPresenter.WaveActionHandler actionHandler =
new SearchPresenter.WaveActionHandler() {
@Override
public void onCreateWave() {
ClientEvents.get().fireEvent(new WaveCreationEvent());
}
@Override
public void onWaveSelected(final WaveId id) {
ClientEvents.get().fireEvent(new WaveSelectionEvent(WaveRef.of(id)));
}
};
search = SimpleSearch.create(RemoteSearchService.create(), waveStore);
SearchPresenter.create(search, searchPanel, actionHandler, profiles);
}
/**
* Setup ui.
*/
private void setupUi() {
// Set up UI
final FlowPanel self = BINDER.createAndBindUi(this);
// kune-patch
// RootPanel.get("app").add(self);
InitialsResources.INS.css().ensureInjected();
initWidget(self);
self.getElement().getStyle().clearPosition();
// splitPanel.setWidgetMinSize(searchPanel, 300);
AttachmentManagerProvider.init(AttachmentManagerImpl.getInstance());
setupSearchPanel();
setupWavePanel();
}
/**
* Setup wave panel.
*/
private void setupWavePanel() {
// Hide the frame until waves start getting opened.
UIObject.setVisible(flow.getElement(), false);
// Handles opening waves.
ClientEvents.get().addWaveSelectionEventHandler(new WaveSelectionEventHandler() {
@Override
public void onSelection(final WaveRef waveRef) {
openWave(waveRef, false, null);
}
});
}
/**
* Use socket io.
*
* @return true, if successful
*/
private native boolean webSocketNotSupported() /*-{
return !window.WebSocket
}-*/;
@Override
public WaveStore getWaveStore() {
return waveStore;
}
}