package org.openslx.dozmod.gui.window; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; import org.openslx.bwlp.thrift.iface.*; 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.configurator.NetrulesConfigurator.StateWrapper; import org.openslx.dozmod.gui.configurator.StartupConfigurator.StartupSettings; import org.openslx.dozmod.gui.helper.DateTimeHelper; import org.openslx.dozmod.gui.helper.I18n; import org.openslx.dozmod.gui.helper.MessageType; import org.openslx.dozmod.gui.helper.UiFeedback; import org.openslx.dozmod.gui.panel.ContainerPanel; 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.dozmod.util.ContainerUtils; import org.openslx.thrifthelper.Comparators; import org.openslx.thrifthelper.TConst; import org.openslx.thrifthelper.ThriftManager; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.List; import java.util.*; /** * Window to display and edit the details of a lecture */ public class LectureDetailsWindow extends LectureDetailsWindowLayout implements UiFeedback { /** * Version for serialization. */ private static final long serialVersionUID = -2190481933307215137L; private static final Logger LOGGER = LogManager.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 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.debug("Valid: " + changeMonitor.isValid()); } @Override public void modificationChanged() { btnSaveChanges.setEnabled(changeMonitor.isValid() && changeMonitor.wasEverModified()); //LOGGER.debug("Changed: " + changeMonitor.isCurrentlyModified()); } }); // Add controls to change monitor changeMonitor.addFixedCombo(cboVersions, Comparators.imageVersionDetails) .addConstraint(new DialogChangeMonitor.ValidationConstraint() { public String checkStateValid(ImageVersionDetails userInput) { if (userInput != null && userInput.isValid) return null; return I18n.WINDOW.getString("LectureDetails.ComboBox.versions.error"); } }); // 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 dateRangeValidator = new DialogChangeMonitor.ValidationConstraint() { public String checkStateValid(Object ignored) { Date start = DateTimeHelper.getDateFrom(dtpStartDate, spnStartTime); Date end = DateTimeHelper.getDateFrom(dtpEndDate, spnEndTime); if (!end.after(start)) return I18n.WINDOW.getString("LectureDetails.Message.error.endAfterStart"); if (end.after(maxValidity)) return I18n.WINDOW.getString("LectureDetails.Message.error.endAfterMaxValidity", 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( I18n.WINDOW.getString("LectureDetails.TextField.title.error"))); changeMonitor.add(txtDescription).addConstraint(new TextNotEmptyConstraint( I18n.WINDOW.getString("LectureDetails.TextField.description.error"))); 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( I18n.WINDOW.getString("LectureDetails.NetRulesConfigurator.error"))); 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; if (ContainerUtils.isContainerImageLinked(newImage)) { ContainerUtils.showWarning(me, I18n.PAGE.getString("LectureImageList.WizardPage.errorMessage.containerLinkedWithLecture") ,LOGGER); return; } else if (ContainerUtils.isDataContainer(newImage)) { ContainerUtils.showWarning(me, I18n.PAGE.getString("LectureImageList.WizardPage.errorMessage.imageIsDataContainer") ,LOGGER); return; } final ImageMetaCallback callback = new ImageMetaCallback() { @Override public void fetchedImageDetails(ImageDetailsRead imageDetails, Map 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()); initContainerPanel(); } }; 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, I18n.WINDOW.getString("LectureDetails.Message.yesNo.changeOwner"), MessageType.QUESTION_YESNO, null, null)) setLectureOwner(user); } }, I18n.WINDOW.getString("LectureDetails.Button.changeOwner.caption"), 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()); } initContainerPanel(); // 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); ctlNetshareConfigurator.setState(lecture.networkShares, lecture.presetNetworkShares); ctlLdapFilterConfigurator.setState(lecture.ldapFilters, lecture.presetLdapFilters); ctlNetrulesConfigurator.setState(lecture); 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(I18n.WINDOW_LAYOUT.getString("LectureDetails.Tab.info.title"))); setVisible(true); changeMonitor.reset(); } private void initContainerPanel() { if (image != null) { if (image.getVirtId().equals(TConst.VIRT_DOCKER)) { addContainerTab(); pnlTabContainer.init(Session.getSatelliteToken(), image, ContainerPanel.CONTAINER_CONTEXT); pnlTabContainer.addToChangeMonitor(changeMonitor); } else { removeContainerTab(); } } } /** * Helper to fill the combobox with the versions of the image. The list will be * sorted by creation timestamp */ private void fillVersionsCombo() { List versions; if (image == null) { versions = new ArrayList<>(0); } else { versions = image.getVersions(); } // version combo for (Iterator 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() { public int compare(ImageVersionDetails o1, ImageVersionDetails o2) { return -Long.compare(o1.getCreateTime(), o2.getCreateTime()); } }); cboVersions.setModel(new DefaultComboBoxModel( versions.toArray(new ImageVersionDetails[versions.size()]))); cboVersions.setSelectedIndex(-1); // To make the change monitor happy 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, I18n.WINDOW.getString("LectureDetails.Message.info.setLectureOwner", 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, I18n.WINDOW.getString("LectureDetails.Message.error.imageInvalid"), 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, I18n.WINDOW.getString("LectureDetails.Message.error.imageVersionInvalid"), MessageType.ERROR, null, null); return; } versionSize = version.fileSize; break; } } if (versionSize == 0) { Gui.showMessageBox(this, I18n.WINDOW.getString("LectureDetails.Message.error.imageVersionFileSizeInvalid"), 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 StartupSettings startupSettings = ctlRunscriptConfigurator.getState(); final LectureWrite metadata = new LectureWrite(txtTitle.getText(), txtDescription.getText(), lecture.getImageVersionId(), chkAutoUpdate.isSelected(), chkIsActive.isSelected(), startTime, endTime, startupSettings.serializeItems(), null, chkIsExam.isSelected(), chkHasInternetAccess.isSelected(), lecture.getDefaultPermissions(), ctlLocationSelector.getSelectedLocationsAsIds(), ctlLocationSelector.getOnlyInSelection(), // TODO limitOnlyToAllowedUsers, default to false for now false, chkHasUsbAccess.isSelected()); StateWrapper netrules = ctlNetrulesConfigurator.getState(); metadata.setNetworkExceptions(netrules.customRules); metadata.setPresetNetworkExceptionIds(netrules.selectedPresets); metadata.setNetworkShares(ctlNetshareConfigurator.getState()); metadata.setLdapFilters(ctlLdapFilterConfigurator.getState()); metadata.setPresetScriptIds(startupSettings.selectedScripts); // TODO maybe there could be a nicer way to distinguish between Lectures withs Containers if (image != null && image.getVirtId().equals(TConst.VIRT_DOCKER) ) { if(!pnlTabContainer.saveChanges(Session.getSatelliteToken(),image)) return false; } // 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, I18n.WINDOW.getString("LectureDetails.Message.error.saveChangesInternal")); 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, I18n.WINDOW.getString("LectureDetails.Message.error.saveCustomPermissions")); } 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 = I18n.WINDOW.getString("LectureDetails.Message.error.isPeriodValid"); if (feedback) { Gui.showMessageBox(me, msg, MessageType.ERROR, null, 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 setTabEnabled(pnlTabGeneral, editable); setTabEnabled(pnlTabPermissions, editable && LecturePerms.canAdmin(lecture)); // enable the other tabs that might have been added to the panel (depends on API // version) setTabEnabled(pnlTabRestrictions, editable); setTabEnabled(pnlTabLocations, editable); setTabEnabled(pnlTabRunscript, editable); setTabEnabled(pnlTabNetshare, editable); setTabEnabled(pnlTabLdapFilter, editable); setTabEnabled(pnlTabNetrules, editable); btnChangeOwner.setEnabled(LecturePerms.canAdmin(lecture)); btnDownloadImage.setEnabled(ImagePerms.canDownload(image)); } private void setTabEnabled(JPanel tab, boolean editable) { int index = pnlTabs.indexOfComponent(tab); if (index == -1) // Check if tab exists -- we don't add some, depending on server version return; pnlTabs.setEnabledAt(index, editable); } /** * 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); } @Override public void setVisible(boolean visible) { if (!isVisible()) { pack(); MainWindow.centerShell(this); } super.setVisible(visible); } /* * ***************************************************************************** * ** * * 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, I18n.WINDOW.getString("LectureDetails.Message.yesNo.safeClose"), MessageType.QUESTION_YESNO, null, null)) return; synchronized (me) { lecture = null; image = null; } dispose(); } }