/*******************************************************************************
*Copyright (c) 2009 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
*
* This file 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 General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Please contact Eucalyptus Systems, Inc., 130 Castilian
* Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/>
* if you need additional information or have any questions.
*
* This file may incorporate work covered under the following copyright and
* permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms, with
* or without modification, are permitted provided that the following
* conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF
* THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE
* LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS
* SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA
* BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN
* THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
* OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR
* WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH
* ANY SUCH LICENSES OR RIGHTS.
*******************************************************************************/
package edu.ucsb.eucalyptus.admin.client.extensions.store;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.core.client.GWT;
public class ImageStoreWidget extends Composite {
private final ImageStoreClient client;
private SimplePanel imageSectionsPanelContainer = new SimplePanel();
private VerticalPanel imageSectionsPanel = new VerticalPanel();
private StatusWidget statusWidget = new StatusWidget();
private SearchWidget searchWidget = new SearchWidget();
private Label errorLabel = new Label();
private boolean errorOnUserAction = false;
private ResponseHandler responseHandler = new ResponseHandler();
private ResponseHandler searchResponseHandler = new SearchResponseHandler();
private ResponseHandler userRequestResponseHandler = new UserRequestResponseHandler();
private RunActionInstallHandler runActionInstallHandler = new RunActionInstallHandler();
private RunActionCancelHandler runActionCancelHandler = new RunActionCancelHandler();
private RunActionClearErrorHandler runActionClearErrorHandler = new RunActionClearErrorHandler();
private ReloadTimer reloadTimer = new ReloadTimer();
private static class ImageData {
ImageInfo info = null;
ImageState state = null;
List<ImageWidget> widgets = null;
ImageWidget createImageWidget() {
ImageWidget imageWidget = null;
if (info != null && state != null) {
if (widgets == null) {
widgets = new ArrayList<ImageWidget>();
}
imageWidget = new ImageWidget(info, state);
widgets.add(imageWidget);
}
return imageWidget;
}
}
private Map<String,ImageData> imageMap = new HashMap<String,ImageData>();
public ImageStoreWidget(ImageStoreClient client) {
this.client = client;
final VerticalPanel verticalPanel = new VerticalPanel();
verticalPanel.add(searchWidget);
verticalPanel.add(errorLabel);
verticalPanel.add(statusWidget);
verticalPanel.add(imageSectionsPanelContainer);
imageSectionsPanelContainer.setWidget(imageSectionsPanel);
imageSectionsPanelContainer.setStyleName("istore-sections-panel");
initWidget(verticalPanel);
setStyleName("istore-image-store-widget");
errorLabel.setStyleName("istore-global-error-message");
reloadTimer.scheduleRepeating(5000);
client.getDashboard(responseHandler);
searchWidget.addSearchHandler(new SearchHandler<SearchWidget>() {
public void onSearch(SearchEvent<SearchWidget> searchEvent) {
String searchText = searchEvent.getSearchText();
// The searchResponseHandler will remove the "in progress"
// status which is set automatically by the widget itself.
if (searchText.length() == 0) {
ImageStoreWidget.this.client.getDashboard(searchResponseHandler);
} else {
ImageStoreWidget.this.client.search(searchText, searchResponseHandler);
}
}
});
statusWidget.addClearErrorHandler(runActionClearErrorHandler);
}
private void updateDisplay(ImageStoreResponse response,
boolean updateRequestedByUser) {
/* The following logic ensures that errors returned as a result
* of a user action won't be overriden by an error generated by
* an automatic update. Also, automatic updates which succeed
* won't erase an error which was caused by a user action, so
* an error which resulted from a user action will only be
* cleared by another successful user action, or replaced by
* another user action error. */
String errorMessage = response.getErrorMessage();
if (!errorOnUserAction || updateRequestedByUser) {
if (errorMessage != null) {
errorOnUserAction = updateRequestedByUser;
if (errorMessage.equalsIgnoreCase("Proxy error: Connection refused")) {
errorMessage = "Error: failed to connect to local store proxy. Is it installed?";
}
errorLabel.setText(errorMessage);
errorLabel.setVisible(true);
} else {
errorOnUserAction = false;
errorLabel.setVisible(false);
}
}
if (response.hasImageSections()) {
// If we got a full new payload, reset the in progress listing,
// otherwise just update it.
statusWidget.clear();
}
for (ImageInfo imageInfo : response.getImageInfos()) {
putImageInfo(imageInfo);
}
for (ImageState imageState : response.getImageStates()) {
putImageState(imageState);
}
if (response.hasImageSections()) {
imageSectionsPanel = new VerticalPanel();
for (ImageSection imageSection : response.getImageSections()) {
addImageSection(imageSection);
}
imageSectionsPanelContainer.setWidget(imageSectionsPanel);
}
}
private void putImageInfo(ImageInfo imageInfo) {
statusWidget.putImageInfo(imageInfo);
ImageData imageData = imageMap.get(imageInfo.getUri());
if (imageData == null) {
imageData = new ImageData();
imageMap.put(imageInfo.getUri(), imageData);
}
imageData.info = imageInfo;
}
private void putImageState(ImageState imageState) {
statusWidget.putImageState(imageState);
ImageData imageData = imageMap.get(imageState.getImageUri());
if (imageData == null) {
imageData = new ImageData();
imageData.state = imageState;
imageMap.put(imageState.getImageUri(), imageData);
} else {
imageData.state = imageState;
if (imageData.widgets != null) {
for (ImageWidget imageWidget : imageData.widgets) {
imageWidget.setImageState(imageState);
}
}
}
}
private ImageWidget createImageWidget(String imageUri) {
ImageData imageData = imageMap.get(imageUri);
if (imageData != null) {
ImageWidget imageWidget = imageData.createImageWidget();
if(imageWidget != null) {
imageWidget.addInstallHandler(runActionInstallHandler);
imageWidget.addCancelHandler(runActionCancelHandler);
imageWidget.addClearErrorHandler(runActionClearErrorHandler);
return imageWidget;
}
}
return null;
}
private void addImageSection(ImageSection imageSection) {
ImageSectionWidget imageSectionWidget = new ImageSectionWidget(imageSection);
for (String uri : imageSection.getImageUris()) {
ImageWidget imageWidget = createImageWidget(uri);
if (imageWidget != null) {
imageSectionWidget.addImageWidget(imageWidget);
}
}
imageSectionsPanel.add(imageSectionWidget);
}
private void reloadStates() {
List<ImageInfo> imageInfos = new ArrayList<ImageInfo>();
for (ImageData imageData : imageMap.values()) {
if (imageData.info != null) {
imageInfos.add(imageData.info);
}
}
// Must ask even if there are no infos currently known,
// since it is possible that the proxy will return other
// details about other on going changes.
client.getImageStates(imageInfos, responseHandler);
}
private class ReloadTimer extends Timer {
public void run() {
if (isVisible()) {
reloadStates();
}
}
}
private class ResponseHandler implements AsyncCallback<ImageStoreResponse> {
protected boolean wasRequestedByUser = false;
public void responseReceived() {}
public void onSuccess(ImageStoreResponse response) {
responseReceived();
updateDisplay(response, wasRequestedByUser);
}
public void onFailure(Throwable caught) {
responseReceived();
updateDisplay(JSONImageStoreResponse.fromString("{}"),
wasRequestedByUser);
errorLabel.setText("Error: " + caught.getMessage());
errorLabel.setVisible(true);
errorOnUserAction = errorOnUserAction || wasRequestedByUser;
}
}
private class UserRequestResponseHandler extends ResponseHandler {
{ wasRequestedByUser = true; }
}
private class SearchResponseHandler extends UserRequestResponseHandler {
public void responseReceived() {
searchWidget.setInProgress(false);
}
}
private class ActionResponseHandler extends UserRequestResponseHandler {
private ImageWidget imageWidget;
public ActionResponseHandler(ImageWidget imageWidget) {
this.imageWidget = imageWidget;
}
public void responseReceived() {
super.responseReceived();
imageWidget.setInProgress(false);
}
}
private class RunActionInstallHandler implements InstallHandler<ImageWidget> {
public void onInstall(InstallEvent<ImageWidget> event) {
ImageWidget targetWidget = event.getTarget();
ImageState imageState = targetWidget.getImageState();
client.runAction(imageState, ImageState.Action.INSTALL,
new ActionResponseHandler(targetWidget));
}
}
private class RunActionCancelHandler implements CancelHandler<ImageWidget> {
public void onCancel(CancelEvent<ImageWidget> event) {
ImageWidget targetWidget = event.getTarget();
ImageState imageState = targetWidget.getImageState();
client.runAction(imageState, ImageState.Action.CANCEL,
new ActionResponseHandler(targetWidget));
}
}
private class RunActionClearErrorHandler implements ClearErrorHandler<ImageState> {
public void onClearError(ClearErrorEvent<ImageState> event) {
client.runAction(event.getTarget(), ImageState.Action.CLEAR_ERROR,
userRequestResponseHandler);
}
}
}