package org.openslx.dozmod.gui.window;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.ImagePermissions;
import org.openslx.bwlp.thrift.iface.ImageSummaryRead;
import org.openslx.bwlp.thrift.iface.ImageVersionDetails;
import org.openslx.bwlp.thrift.iface.LecturePermissions;
import org.openslx.bwlp.thrift.iface.LectureRead;
import org.openslx.bwlp.thrift.iface.LectureWrite;
import org.openslx.bwlp.thrift.iface.NetRule;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.MainWindow;
import org.openslx.dozmod.gui.changemonitor.AbstractControlWrapper;
import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor;
import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.NotNullConstraint;
import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.TextNotEmptyConstraint;
import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
import org.openslx.dozmod.gui.helper.DateTimeHelper;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.helper.UiFeedback;
import org.openslx.dozmod.gui.window.UserListWindow.UserAddedCallback;
import org.openslx.dozmod.gui.window.layout.LectureDetailsWindowLayout;
import org.openslx.dozmod.permissions.ImagePerms;
import org.openslx.dozmod.permissions.LecturePerms;
import org.openslx.dozmod.thrift.ImageLocalDetailsActions;
import org.openslx.dozmod.thrift.Session;
import org.openslx.dozmod.thrift.ThriftActions;
import org.openslx.dozmod.thrift.ThriftActions.DownloadCallback;
import org.openslx.dozmod.thrift.ThriftActions.ImageMetaCallback;
import org.openslx.dozmod.thrift.ThriftActions.LectureMetaCallback;
import org.openslx.dozmod.thrift.ThriftError;
import org.openslx.dozmod.thrift.cache.UserCache;
import org.openslx.dozmod.util.FormatHelper;
import org.openslx.thrifthelper.Comparators;
import org.openslx.thrifthelper.ThriftManager;
/**
* Window to display and edit the details of a lecture
*/
@SuppressWarnings("serial")
public class LectureDetailsWindow extends LectureDetailsWindowLayout implements UiFeedback {
private static final Logger LOGGER = Logger.getLogger(LectureDetailsWindow.class);
/**
* Self-reference
*/
private final LectureDetailsWindow me = this;
/**
* Callback interface to refresh lecture list after changing lecture details
*/
public interface LectureUpdatedCallback {
public void updated(boolean success);
}
/**
* Callback to be called when changing lecture details on the server
*/
private LectureUpdatedCallback callback = null;
/**
* Lecture that this window shows the details of
*/
private LectureRead lecture = null;
/**
* Action handler proxying thrift calls for image related operations
*/
private final ImageLocalDetailsActions actionHandler;
/**
* Image, that the lecture is linked to.
*/
private ImageDetailsRead image = null;
/**
* Per-User permissions of this lecture
*/
private Map<String, LecturePermissions> customPermissions;
/**
* Did the user have admin rights due to default permissions?
*/
private boolean adminRightsFromDefaultPermissions;
private final DialogChangeMonitor changeMonitor;
private final AbstractControlWrapper<?> changeListenerPermissions;
/**
* Constructor
*
* @param modalParent parent of this popup window
* @param callback function to be called when a lecture update occured
*/
public LectureDetailsWindow(Frame modalParent, LectureUpdatedCallback callback) {
super(modalParent);
// save callback
this.callback = callback;
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
// Set up change monitor
changeMonitor = new DialogChangeMonitor(new DialogChangeMonitor.Callback() {
@Override
public void validityChanged(String errorMessage) {
lblError.setText(errorMessage);
btnSaveChanges.setEnabled(changeMonitor.isValid() && changeMonitor.wasEverModified());
LOGGER.info("Valid: " + changeMonitor.isValid());
}
@Override
public void modificationChanged() {
btnSaveChanges.setEnabled(changeMonitor.isValid() && changeMonitor.wasEverModified());
LOGGER.info("Changed: " + changeMonitor.isCurrentlyModified());
}
});
// Add controls to change monitor
changeMonitor.addFixedCombo(cboVersions, Comparators.imageVersionDetails)
.addConstraint(
new DialogChangeMonitor.ValidationConstraint<ImageVersionDetails>() {
public String checkStateValid(ImageVersionDetails userInput) {
if (userInput != null && userInput.isValid)
return null;
return "Keine/Ungültige VM-Version ausgewählt";
}
});
// Create constraint for date start/end
final Date maxValidity;
Calendar c = Calendar.getInstance();
c.add(Calendar.DAY_OF_MONTH, Session.getSatelliteConfig().maxLectureValidityDays);
maxValidity = c.getTime();
ValidationConstraint<Object> dateRangeValidator = new DialogChangeMonitor.ValidationConstraint<Object>() {
public String checkStateValid(Object ignored) {
Date start = DateTimeHelper.getDateFrom(dtpStartDate, spnStartTime);
Date end = DateTimeHelper.getDateFrom(dtpEndDate, spnEndTime);
if (!end.after(start))
return "Enddatum darf nicht vor dem Startdatum liegen";
if (end.after(maxValidity))
return "Enddatum liegt nach dem " + FormatHelper.shortDate(maxValidity);
return null;
}
};
changeMonitor.add(chkAutoUpdate);
changeMonitor.add(chkIsExam);
changeMonitor.add(chkHasInternetAccess);
changeMonitor.add(chkHasUsbAccess);
changeMonitor.add(chkIsActive);
changeMonitor.add(chkCustomPermAdmin);
changeMonitor.add(chkCustomPermEdit);
changeMonitor.add(txtTitle).addConstraint(new TextNotEmptyConstraint("Veranstaltungsname darf nicht leer sein"));
changeMonitor.add(txtDescription).addConstraint(new TextNotEmptyConstraint("Beschreibung darf nicht leer sein"));
changeMonitor.add(dtpEndDate).addConstraint(dateRangeValidator);
changeMonitor.add(dtpStartDate).addConstraint(dateRangeValidator);
changeMonitor.add(spnEndTime).addConstraint(dateRangeValidator);
changeMonitor.add(spnStartTime).addConstraint(dateRangeValidator);
changeMonitor.add(ctlNetrulesConfigurator).addConstraint(new NotNullConstraint<List<NetRule>>("Fehlerhafte Netzwerkregeln"));
changeListenerPermissions = changeMonitor.add(ctlPermissionManager);
ctlLocationSelector.addToChangeMonitor(changeMonitor);
ctlRunscriptConfigurator.addToChangeMonitor(changeMonitor);
ctlNetshareConfigurator.addToChangeMonitor(changeMonitor);
ctlLdapFilterConfigurator.addToChangeMonitor(changeMonitor);
// TODO: LDAP/NetShare: Having uncommitted changes in the input fields should be handled too
// End change monitor
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
safeClose();
}
});
actionHandler = new ImageLocalDetailsActions(JOptionPane.getFrameForComponent(LectureDetailsWindow.this));
btnLinkImage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final ImageSummaryRead newImage = LectureChangeImage.open(LectureDetailsWindow.this, changeMonitor);
if (newImage == null)
return;
final ImageMetaCallback callback = new ImageMetaCallback() {
@Override
public void fetchedImageDetails(ImageDetailsRead imageDetails,
Map<String, ImagePermissions> permissions) {
if (imageDetails == null) {
return;
}
synchronized (me) {
image = imageDetails;
}
lecture.imageBaseId = newImage.imageBaseId;
lecture.imageVersionId = newImage.latestVersionId;
fillVersionsCombo();
cboVersions.setEnabled(false);
chkAutoUpdate.setSelected(true);
txtImageName.setText(newImage.getImageName());
}
};
actionHandler.getImageDetails(newImage.imageBaseId, callback);
}
});
btnClose.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
safeClose();
}
});
btnDownloadImage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performImageDownload();
}
});
chkAutoUpdate.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
cboVersions.setEnabled(!chkAutoUpdate.isSelected());
}
});
btnChangeOwner.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UserListWindow.open(me, new UserAddedCallback() {
@Override
public void userAdded(UserInfo user, UserListWindow window) {
window.dispose();
if (Gui.showMessageBox(me, "Sind Sie sicher, dass sie die Besitzerrechte an "
+ "einen anderen Account übertragen wollen?", MessageType.QUESTION_YESNO,
LOGGER, null))
setLectureOwner(user);
}
}, "Besitzer festlegen", lecture.ownerId);
}
});
// Update default permissions in the permission manager immediately, so it affects
// newly added users
final ItemListener updateDefaultPermissionListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getSource() == chkCustomPermAdmin) {
lecture.defaultPermissions.admin = chkCustomPermAdmin.isSelected();
} else {
lecture.defaultPermissions.edit = chkCustomPermEdit.isSelected();
}
}
};
chkCustomPermAdmin.addItemListener(updateDefaultPermissionListener);
chkCustomPermEdit.addItemListener(updateDefaultPermissionListener);
// last step, the save button
btnSaveChanges.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
saveChanges();
}
});
// disable save button
btnSaveChanges.setEnabled(false);
// wait for the image to be loaded before (potentially) enabling fields
toggleEditable(false);
}
/**
* Sets the lecture to show the details of by setting the 'lecture' and
* 'image'
* members to its metadata. This method will fetch the information from the
* sat
*
* @param lectureId the id of the lecture to be displayed
*/
public void setLecture(final String lectureId) {
ThriftActions.getLectureAndImageDetails(JOptionPane.getFrameForComponent(me), lectureId,
new LectureMetaCallback() {
@Override
public void fetchedLectureAndImageDetails(LectureRead lectureDetails,
ImageDetailsRead imageDetails) {
synchronized (me) {
lecture = lectureDetails;
image = imageDetails;
if (lecture != null) {
customPermissions = ThriftActions.getLecturePermissions(
JOptionPane.getFrameForComponent(me), lecture.lectureId);
}
fillDetails();
}
}
});
}
/**
* Internal callback function when we received the lecture's details from the server
*/
private void fillDetails() {
if (lecture == null) {
txtTitle.setText("-");
lblTitleInfo.setText("-");
toggleEditable(false);
return;
}
if (image == null) {
txtImageName.setText("-");
lblImageNameInfo.setText("-");
} else {
txtImageName.setText(image.getImageName());
lblImageNameInfo.setText(image.getImageName());
}
// init permission info
adminRightsFromDefaultPermissions = lecture.defaultPermissions.admin;
ctlPermissionManager.initPanel(customPermissions, lecture.defaultPermissions, lecture.ownerId);
chkCustomPermAdmin.setSelected(lecture.defaultPermissions.admin);
chkCustomPermEdit.setSelected(lecture.defaultPermissions.edit);
// init location info
ctlLocationSelector.setOnlyInSelection(lecture.limitToLocations);
ctlLocationSelector.setSelectedLocationsAsIds(lecture.locationIds);
// init advanced info
ctlRunscriptConfigurator.setState(lecture.runscript);
ctlNetshareConfigurator.setState(lecture.networkShares);
ctlLdapFilterConfigurator.setState(lecture.ldapFilters);
ctlNetrulesConfigurator.setState(lecture.networkExceptions);
txtTitle.setText(lecture.getLectureName());
lblTitleInfo.setText(lecture.getLectureName());
txtDescription.setText(lecture.getDescription());
lblOwner.setUser(UserCache.find(lecture.getOwnerId()));
lblOwnerInfo.setUser(UserCache.find(lecture.getOwnerId()));
lblUpdater.setUser(UserCache.find(lecture.getUpdaterId()));
lblCreateTime.setText(FormatHelper.longDate(lecture.getCreateTime()));
lblUpdateTime.setText(FormatHelper.longDate(lecture.getUpdateTime()));
lblStartTime.setText(FormatHelper.longDate(lecture.getStartTime()));
lblEndTime.setText(FormatHelper.longDate(lecture.getEndTime()));
txtId.setText(lecture.getLectureId());
chkIsActive.setSelected(lecture.isEnabled);
chkHasInternetAccess.setSelected(lecture.hasInternetAccess);
chkHasUsbAccess.setSelected(lecture.hasUsbAccess);
chkIsExam.setSelected(lecture.isExam);
chkAutoUpdate.setSelected(lecture.autoUpdate);
cboVersions.setEnabled(!lecture.autoUpdate);
lblUseCount.setText(Integer.toString(lecture.useCount));
fillVersionsCombo();
Calendar startCal = Calendar.getInstance();
startCal.setTime(new Date(lecture.getStartTime() * 1000l));
dtpStartDate.getModel().setDate(startCal.get(Calendar.YEAR), startCal.get(Calendar.MONTH),
startCal.get(Calendar.DATE));
spnStartTime.getModel().setValue(startCal.getTime());
Calendar endCal = Calendar.getInstance();
endCal.setTime(new Date(lecture.getEndTime() * 1000l));
dtpEndDate.getModel().setDate(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH),
endCal.get(Calendar.DATE));
spnEndTime.getModel().setValue(endCal.getTime());
// now enable the tabs the user can see given its permissions
toggleEditable(true);
// and always switch to the "About" tab
pnlTabs.setSelectedIndex(pnlTabs.indexOfTab("Übersicht"));
setVisible(true);
changeMonitor.reset();
}
/**
* Helper to fill the combobox with the versions of the image. The list will
* be sorted by creation timestamp
*/
private void fillVersionsCombo() {
List<ImageVersionDetails> versions;
if (image == null) {
versions = new ArrayList<>(0);
} else {
versions = image.getVersions();
}
// version combo
for (Iterator<ImageVersionDetails> it = versions.iterator(); it.hasNext();) {
ImageVersionDetails version = it.next();
if (!version.isValid && !lecture.imageVersionId.equals(version.versionId)) {
it.remove();
}
}
// sort versions by createtime
Collections.sort(versions, new Comparator<ImageVersionDetails>() {
public int compare(ImageVersionDetails o1, ImageVersionDetails o2) {
return -Long.compare(o1.getCreateTime(), o2.getCreateTime());
}
});
cboVersions.setModel(new DefaultComboBoxModel<ImageVersionDetails>(
versions.toArray(new ImageVersionDetails[versions.size()])));
cboVersions.setSelectedItem(new ImageVersionDetails(lecture.getImageVersionId(), 0, 0, 0, null, true,
true, true, null));
}
/**
* Sets the lecture's owner to the given user
*
* @param user UserInfo representation of the new owner
*/
private void setLectureOwner(final UserInfo user) {
if (!ThriftActions.setLectureOwner(JOptionPane.getFrameForComponent(this), lecture.getLectureId(),
user)) {
return;
}
// success
Gui.showMessageBox(me, "Besitzrechte übertragen an " + FormatHelper.userName(user), MessageType.INFO,
null, null);
toggleEditable(false);
String lectureId = lecture.getLectureId();
synchronized (me) {
image = null;
lecture = null;
}
setLecture(lectureId);
}
/**
* Triggers the download of the currently used image version of the lecture
*/
private void performImageDownload() {
if (image == null) {
Gui.showMessageBox(this, "VM ungültig.", MessageType.ERROR, null, null);
return;
}
btnDownloadImage.setEnabled(false);
long versionSize = 0;
for (ImageVersionDetails version : image.versions) {
if (version.versionId.equals(lecture.imageVersionId)) {
if (!version.isValid) {
Gui.showMessageBox(this, "Ungültige VM-Version gewählt", MessageType.ERROR, null, null);
return;
}
versionSize = version.fileSize;
break;
}
}
if (versionSize == 0) {
Gui.showMessageBox(this, "Fehler bei der Abfrage der Größe des VM-Abbildes.", MessageType.ERROR, null,
null);
return;
}
ThriftActions.initDownload(JOptionPane.getFrameForComponent(this), lecture.imageVersionId,
image.imageName, image.virtId, image.osId, versionSize, new DownloadCallback() {
@Override
public void downloadInitialized(boolean success) {
if (!success) {
Gui.asyncExec(new Runnable() {
@Override
public void run() {
btnDownloadImage.setEnabled(true);
}
});
}
}
});
}
/**
* Triggers the saving of the changes of this lecture
* And inform the callback function about the outcome
*/
private void saveChanges() {
boolean saved = saveChangesInternal();
callback.updated(saved);
if (saved) {
dispose();
} else {
btnSaveChanges.setEnabled(true);
}
}
/**
* Internal helper actually saving the changes to the satellite
*
* @return true if saving succeeded, false otherwise.
*/
private boolean saveChangesInternal() {
long startTime = DateTimeHelper.getDateFrom(dtpStartDate, spnStartTime).getTime() / 1000L;
long endTime = DateTimeHelper.getDateFrom(dtpEndDate, spnEndTime).getTime() / 1000L;
if (!isPeriodValid(startTime, endTime, true))
return false;
// check, whether autoupdate is selected and choose version accordingly
if (image != null) {
lecture.imageVersionId = chkAutoUpdate.isSelected() ? image.latestVersionId
: cboVersions.getItemAt(cboVersions.getSelectedIndex()).versionId;
}
// Special case similar to ImageDetailsWindow:if the user had admin rights
// through default permissions, first save custom permissions.
if (adminRightsFromDefaultPermissions && changeListenerPermissions.isCurrentlyChanged()) {
if (!saveCustomPermissions()) {
return false;
}
changeListenerPermissions.reset();
}
// first build the LectureWrite from the GUI fields
final LectureWrite metadata = new LectureWrite(txtTitle.getText(), txtDescription.getText(),
lecture.getImageVersionId(), chkAutoUpdate.isSelected(), chkIsActive.isSelected(),
startTime, endTime,
ctlRunscriptConfigurator.getState(), null,
chkIsExam.isSelected(),
chkHasInternetAccess.isSelected(),
lecture.getDefaultPermissions(), ctlLocationSelector.getSelectedLocationsAsIds(),
ctlLocationSelector.getOnlyInSelection(),
// TODO limitOnlyToAllowedUsers, default to false for now
false, chkHasUsbAccess.isSelected());
metadata.setNetworkExceptions(ctlNetrulesConfigurator.getState());
metadata.setNetworkShares(ctlNetshareConfigurator.getState());
metadata.setLdapFilters(ctlLdapFilterConfigurator.getState());
// now trigger the actual action
try {
ThriftManager.getSatClient().updateLecture(Session.getSatelliteToken(),
lecture.getLectureId(), metadata);
LOGGER.info("Successfully saved new metadata");
} catch (TException e) {
ThriftError.showMessage(JOptionPane.getFrameForComponent(this), LOGGER, e,
"Fehler beim Speichern der Veranstaltung!");
return false;
}
if (changeListenerPermissions.isCurrentlyChanged()) {
if (!saveCustomPermissions()) {
return false;
}
}
changeMonitor.reset();
return true;
}
/**
* Helper to save custom user permissions.
*
* @return true if successfully saved, false otherwise.
*/
private boolean saveCustomPermissions() {
try {
ThriftManager.getSatClient().writeLecturePermissions(Session.getSatelliteToken(),
lecture.lectureId, ctlPermissionManager.getPermissions());
LOGGER.info("Successfully saved custom permissions");
return true;
} catch (TException e) {
ThriftError.showMessage(JOptionPane.getFrameForComponent(this), LOGGER, e,
"Fehler beim Übertragen der Berechtigungen!");
}
return false;
}
/**
* Checks if the given start and end date represent a valid time period.
* This is the case, if start < end and if current time < end
*
* @param start date of the period to check
* @param end date of the period to check
* @param feedback true if the user should be shown feedback, false
* otherwise
* @return true if the period is valid, false otherwise
*/
private boolean isPeriodValid(long start, long end, boolean feedback) {
if (start <= 0 || end <= 0)
return false;
// analyse time stuff to see if its valid
if (end > start)
return true;
String msg = "Start der Veranstaltung ist nach dem Enddatum!";
if (feedback) {
Gui.showMessageBox(me, msg, MessageType.ERROR,
LOGGER, null);
}
lblError.setText(msg);
return false;
}
/**
* Enables/Disables the tabs based on the given flag 'editable'.
*
* @param editable when true, will enable the tabs if the user is allowed to see them.
* If false, this will disable all tabs but the first tab "About".
*/
protected void toggleEditable(boolean editable) {
// if we don't have a lecture and an image set, just disable
editable &= (LecturePerms.canEdit(lecture));
// enable the standard tabs that are always enabled
pnlTabs.setEnabledAt(pnlTabs.indexOfTab("Allgemein"), editable);
pnlTabs.setEnabledAt(pnlTabs.indexOfTab("Berechtigungen"), editable && LecturePerms.canAdmin(lecture));
// enable the other tabs that might have been added to the panel (depends on API version)
if (pnlTabs.indexOfTab("Beschränkungen") != -1) {
pnlTabs.setEnabledAt(pnlTabs.indexOfTab("Beschränkungen"), editable);
}
if (pnlTabs.indexOfTab("Raumauswahl") != -1) {
pnlTabs.setEnabledAt(pnlTabs.indexOfTab("Raumauswahl"), editable);
}
if (pnlTabs.indexOfTab("Startskript") != -1) {
pnlTabs.setEnabledAt(pnlTabs.indexOfTab("Startskript"), editable);
}
if (pnlTabs.indexOfTab("Netzlaufwerke") != -1) {
pnlTabs.setEnabledAt(pnlTabs.indexOfTab("Netzlaufwerke"), editable);
}
btnChangeOwner.setEnabled(LecturePerms.canAdmin(lecture));
btnDownloadImage.setEnabled(ImagePerms.canDownload(image));
}
/**
* Opens a new LectureDetailsWindow showing the details of the
* lecture with ID = lectureId
*
* @param modalParent parent of this window
* @param lectureId id of the lecture to set the details of
*/
public static void open(Frame modalParent, String lectureId, LectureUpdatedCallback callback) {
LectureDetailsWindow win = new LectureDetailsWindow(modalParent, callback);
win.setLecture(lectureId);
win.setVisible(true);
}
@SuppressWarnings("deprecation")
@Override
public void show() {
if (!isVisible()) {
pack();
MainWindow.centerShell(this);
}
super.show();
}
/* *******************************************************************************
*
* UIFeedback implementation
*
********************************************************************************/
@Override
public boolean wantConfirmQuit() {
return changeMonitor.isCurrentlyModified();
}
@Override
public void escapePressed() {
// Also ask if applicable
safeClose();
}
/*
* Safe close helper: checks if we have unsaved work and prompt the user for
* confirmation if so
*/
private void safeClose() {
if (changeMonitor.isCurrentlyModified()
&& !Gui.showMessageBox(me, "Änderungen werden verworfen, wollen Sie wirklich schließen?",
MessageType.QUESTION_YESNO, null, null))
return;
synchronized(me) {
lecture = null;
image = null;
}
dispose();
}
}