/*
* 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 org.apache.ace.webui.vaadin;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import org.apache.ace.client.repository.object.ArtifactObject;
import org.apache.ace.client.repository.repository.ArtifactRepository;
import org.apache.ace.connectionfactory.ConnectionFactory;
import org.apache.ace.webui.domain.OBREntry;
import org.apache.ace.webui.vaadin.UploadHelper.ArtifactDropHandler;
import org.apache.ace.webui.vaadin.UploadHelper.GenericUploadHandler;
import org.apache.ace.webui.vaadin.UploadHelper.UploadHandle;
import org.osgi.service.log.LogService;
import com.vaadin.data.Item;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.DragAndDropWrapper;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Upload;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.Reindeer;
/**
* Provides a dialog for uploading new artifacts to ACE, or selecting existing artifacts from the repository.
*/
abstract class AddArtifactWindow extends Window {
private static final String PROPERTY_SYMBOLIC_NAME = "symbolic name";
private static final String PROPERTY_VERSION = "version";
private static final String PROPERTY_PURGE = "purge";
private final String m_repositoryXML;
private final File m_sessionDir;
private final URL m_obrUrl;
private final List<File> m_uploadedArtifacts = new ArrayList<File>();
private final Button m_searchButton;
private final Button m_addButton;
private final Table m_artifactsTable;
/**
* Creates a new {@link AddArtifactWindow} instance.
*
* @param sessionDir
* the session directory to temporary place artifacts in;
* @param obrUrl
* the URL of the OBR to use.
*/
public AddArtifactWindow(File sessionDir, URL obrUrl, String repositoryXML) {
super("Add artifact");
m_sessionDir = sessionDir;
m_obrUrl = obrUrl;
m_repositoryXML = repositoryXML;
setModal(true);
setWidth("50em");
setCloseShortcut(KeyCode.ESCAPE);
m_artifactsTable = new Table("Artifacts in repository");
m_artifactsTable.addContainerProperty(PROPERTY_SYMBOLIC_NAME, String.class, null);
m_artifactsTable.addContainerProperty(PROPERTY_VERSION, String.class, null);
m_artifactsTable.addContainerProperty(PROPERTY_PURGE, Button.class, null);
m_artifactsTable.setSizeFull();
m_artifactsTable.setSelectable(true);
m_artifactsTable.setMultiSelect(true);
m_artifactsTable.setImmediate(true);
m_artifactsTable.setHeight("15em");
final Table uploadedArtifacts = new Table("Uploaded artifacts");
uploadedArtifacts.addContainerProperty(PROPERTY_SYMBOLIC_NAME, String.class, null);
uploadedArtifacts.addContainerProperty(PROPERTY_VERSION, String.class, null);
uploadedArtifacts.setSizeFull();
uploadedArtifacts.setSelectable(false);
uploadedArtifacts.setMultiSelect(false);
uploadedArtifacts.setImmediate(true);
uploadedArtifacts.setHeight("15em");
final GenericUploadHandler uploadHandler = new GenericUploadHandler(m_sessionDir) {
@Override
public void updateProgress(long readBytes, long contentLength) {
// TODO Auto-generated method stub
}
@Override
protected void artifactsUploaded(List<UploadHandle> uploads) {
for (UploadHandle handle : uploads) {
try {
URL artifact = handle.getFile().toURI().toURL();
Item item = uploadedArtifacts.addItem(artifact);
item.getItemProperty(PROPERTY_SYMBOLIC_NAME).setValue(handle.getFilename());
item.getItemProperty(PROPERTY_VERSION).setValue("");
m_uploadedArtifacts.add(handle.getFile());
}
catch (MalformedURLException e) {
showErrorNotification("Upload artifact processing failed", "<br />Reason: " + e.getMessage());
logError("Processing of " + handle.getFilename() + " failed.", e);
}
}
}
};
final Upload uploadArtifact = new Upload();
uploadArtifact.setCaption("Upload Artifact");
uploadHandler.install(uploadArtifact);
final DragAndDropWrapper finalUploadedArtifacts = new DragAndDropWrapper(uploadedArtifacts);
finalUploadedArtifacts.setDropHandler(new ArtifactDropHandler(uploadHandler));
addListener(new Window.CloseListener() {
public void windowClose(CloseEvent e) {
for (File artifact : m_uploadedArtifacts) {
artifact.delete();
}
}
});
HorizontalLayout searchBar = new HorizontalLayout();
searchBar.setMargin(false);
searchBar.setSpacing(true);
final TextField searchField = new TextField();
searchField.setImmediate(true);
searchField.setValue("");
final IndexedContainer dataSource = (IndexedContainer) m_artifactsTable.getContainerDataSource();
m_searchButton = new Button("Search", new ClickListener() {
public void buttonClick(ClickEvent event) {
String searchValue = (String) searchField.getValue();
dataSource.removeAllContainerFilters();
if (searchValue != null && searchValue.trim().length() > 0) {
dataSource.addContainerFilter(PROPERTY_SYMBOLIC_NAME, searchValue,
true /* ignoreCase */, false /* onlyMatchPrefix */);
}
}
});
m_searchButton.setImmediate(true);
searchBar.addComponent(searchField);
searchBar.addComponent(m_searchButton);
m_addButton = new Button("Add", new ClickListener() {
public void buttonClick(ClickEvent event) {
// Import all "local" (existing) bundles...
importLocalBundles(m_artifactsTable);
// Import all "remote" (non existing) bundles...
importRemoteBundles(m_uploadedArtifacts);
close();
}
});
m_addButton.setImmediate(true);
m_addButton.setStyleName(Reindeer.BUTTON_DEFAULT);
m_addButton.setClickShortcut(KeyCode.ENTER);
VerticalLayout layout = (VerticalLayout) getContent();
layout.setMargin(true);
layout.setSpacing(true);
layout.addComponent(searchBar);
layout.addComponent(m_artifactsTable);
layout.addComponent(uploadArtifact);
layout.addComponent(finalUploadedArtifacts);
// The components added to the window are actually added to the window's
// layout; you can use either. Alignments are set using the layout
layout.addComponent(m_addButton);
layout.setComponentAlignment(m_addButton, Alignment.MIDDLE_RIGHT);
searchField.focus();
}
/**
* Shows this dialog on the parent window.
*
* @param parent
* the parent for this window, cannot be <code>null</code>.
*/
public final void showWindow(Window parent) {
try {
// Fill the artifacts table with the data from the OBR...
populateArtifactTable(m_artifactsTable, m_obrUrl);
parent.addWindow(this);
}
catch (Exception e) {
// We've not yet added this window to the given parent, so we cannot use #showErrorNotification here...
parent.showNotification("Failed to retrieve OBR repository!", "Reason: <br/>" + e.getMessage(), Notification.TYPE_ERROR_MESSAGE);
logError("Failed to retrieve OBR repository!", e);
}
}
/**
* Imports all local, i.e., that are already in our local OBR, bundles.
*
* @param artifacts
* the UI-table with artifacts to install, cannot be <code>null</code>.
* @return the imported artifacts, never <code>null</code>.
*/
final List<ArtifactObject> importLocalBundles(final Table artifacts) {
final List<ArtifactObject> added = new ArrayList<ArtifactObject>();
Set<?> selectedItems = (Set<?>) artifacts.getValue();
if (selectedItems != null && !selectedItems.isEmpty()) {
for (Object itemID : selectedItems) {
try {
added.add(UploadHelper.importLocalBundle(getArtifactRepository(), createOBRUrl(itemID)));
}
catch (Exception exception) {
Item item = artifacts.getItem(itemID);
Object symbolicName = item.getItemProperty(PROPERTY_SYMBOLIC_NAME).getValue();
Object version = item.getItemProperty(PROPERTY_VERSION).getValue();
showErrorNotification("Import artifact failed", "Artifact " + symbolicName + " " + version
+ "<br />could not be imported into the repository."
+ "<br />Reason: " + exception.getMessage());
logError("Import of " + symbolicName + " " + version + " failed.", exception);
}
}
}
return added;
}
/**
* Import remote bundles.
*
* @param uploadedArtifacts
* the list with uploaded artifacts, never <code>null</code>.
* @return the list of imported bundles.
*/
final List<ArtifactObject> importRemoteBundles(List<File> uploadedArtifacts) {
List<ArtifactObject> added = new ArrayList<ArtifactObject>();
StringBuffer errors = new StringBuffer();
int failedImports = 0;
for (File artifact : uploadedArtifacts) {
try {
added.add(UploadHelper.importRemoteBundle(getArtifactRepository(), artifact));
}
catch (Exception exception) {
failedImports++;
errors.append("<br />" + exception.getMessage());
logError("Import of " + artifact.getAbsolutePath() + " failed.", exception);
}
finally {
artifact.delete();
}
}
if (failedImports > 0) {
if (failedImports == uploadedArtifacts.size()) {
showErrorNotification("All " + failedImports + " artifacts failed", (failedImports > 30 ? "See the server log for a full list of failures." : errors.toString()));
}
else {
showWarningNotification("" + failedImports + "/" + uploadedArtifacts.size() + " artifacts failed", (failedImports > 30 ? "See the server log for a full list of failures." : errors.toString()));
}
}
return added;
}
/**
* Logs a given message + exception at the error level.
* <p>
* If there's no log service present, this method will silently ignore the log statement.
* </p>
*
* @param aMessage
* the message to log;
* @param aException
* the exception to log.
*/
final void logError(String aMessage, Throwable aException) {
LogService logger = getLogger();
if (logger != null) {
logger.log(LogService.LOG_ERROR, aMessage, aException);
}
}
/**
* Shows an error message on screen.
*
* @param aTitle
* the title of the error message;
* @param aMessage
* the error message itself.
*/
final void showErrorNotification(final String aTitle, final String aMessage) {
getParent().showNotification(aTitle, aMessage, Notification.TYPE_ERROR_MESSAGE);
}
/**
* @return the artifact repository.
*/
protected abstract ArtifactRepository getArtifactRepository();
/**
* @param url
* the URL to connect to, cannot be <code>null</code>.
* @return a valid {@link URLConnection} instance, never <code>null</code>.
*/
protected abstract ConnectionFactory getConnectionFactory();
/**
* @return the log service.
*/
protected abstract LogService getLogger();
/**
* Create a new button that delete an OBREntry on-click.
*
* @param entry
* The entry
* @return The button
*/
private Button createDeleteOBREntryButton(final OBREntry entry) {
Button button = new Button("x");
button.setStyleName(Reindeer.BUTTON_SMALL);
button.setDescription("Delete " + entry.getName());
button.addListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
event.getButton().setEnabled(false);
try {
int code = OBRUtil.deleteOBREntry(getConnectionFactory(), entry, m_obrUrl);
if (code == HttpServletResponse.SC_OK) {
m_artifactsTable.removeItem(entry.getUri());
}
else {
showErrorNotification("Failed to delete resource", "The OBR returned an unexpected response code: " + code);
}
}
catch (IOException e) {
showErrorNotification("Failed to delete resource", "Reason: " + e.getMessage());
}
}
});
return button;
}
private URL createOBRUrl(Object itemID) throws MalformedURLException {
return new URL(m_obrUrl, String.valueOf(itemID));
}
/**
* Gets the bundles.
*
* @param dataSource
* the datasource to fill;
* @param obrBaseUrl
* the obr base url
* @return the bundles
* @throws Exception
* the exception
*/
private void populateArtifactTable(Table dataSource, URL obrBaseUrl) throws Exception {
// retrieve the repository.xml as a stream
List<OBREntry> obrList = OBRUtil.getAvailableOBREntries(getConnectionFactory(), getArtifactRepository(), obrBaseUrl, m_repositoryXML);
if (obrList.isEmpty()) {
logError("No new data in OBR.");
return;
}
// Create a list of all bundle names
for (OBREntry entry : obrList) {
Item item = dataSource.addItem(entry.getUri());
item.getItemProperty(PROPERTY_SYMBOLIC_NAME).setValue(entry.getName());
item.getItemProperty(PROPERTY_VERSION).setValue(entry.getVersion());
item.getItemProperty(PROPERTY_PURGE).setValue(createDeleteOBREntryButton(entry));
}
}
/**
* Logs a given message at the error level.
* <p>
* If there's no log service present, this method will silently ignore the log statement.
* </p>
*
* @param aMessage
* the message to log.
*/
private void logError(String aMessage) {
LogService logger = getLogger();
if (logger != null) {
logger.log(LogService.LOG_ERROR, aMessage);
}
}
/** Shows a warning messsage on screen. */
private void showWarningNotification(final String aTitle, final String aMessage) {
getParent().showNotification(aTitle, aMessage, Notification.TYPE_WARNING_MESSAGE);
}
}