summaryrefslogblamecommitdiffstats
path: root/dozentenmodul/src/main/java/org/openslx/dozmod/gui/control/AdvancedConfigurator.java
blob: eeb4a48359d362e8712fddc029bfa6a4396f1a76 (plain) (tree)
1
2
3
4
5
6
7
8
9
10






                                       
                        

                               







                                 
                                           
                                             









                                                  
                                                        









                                                                           

                                                  

                                                                               

                                                                                    




                                                            















                                                                                                          

         
           


                                                                         
           


                                                                                         
           
                                                 
                                                                   
                                                               
                                                           
                                    
                                                                                       



                            
           





                                                                                    
           

                                                                                     
                                                                                

                                                                                       

                                                                 

         
           


                                                                                 






















                                                                                          

                                                                                       




                                    

                                                                   













                                                                             


                                                                                   
 
                                        





                                                                                 
                                         
                         




                                                                               
                                                                          


                                                                                          

                                                                                                        





                                                                                           
                                                               

                                                                               
                                                              

                                                                                                  
                                                   

                                                                                                

                                      
                                                                 





                                                                                               
 
                                                         
                                                         


                                                                                  

                                                                                                    












                                                                                           
                                                                                      
                                                                                    








                                                                                                
                                                                         




                                                                                                                                    
                                                      








                                                                      

                                                                                
                 
                                                          

                                                                                           

                                                                  




                                         












                                                                                    

                                                                    



                                                                                      
                                                    


                                                              
                                                                                 


                                                                                                 

                                             
                                                                     

                                                                                       








                                                                                                             



                            








                                                                                    


                                                                    


                                                                  
                                                                        
                     






                                                                                                 


                                              




                                                                                                   
                                                                           
 
                                                      


                                                                 



                                                                                   


                                                                                   










                                                                        










































                                                                                             

















                                                                                                                                              
                                                 












                                                                                      
                                                 
 
                                                                             


































                                                                                           

         
package org.openslx.dozmod.gui.control;

import java.awt.Color;
import java.awt.Insets;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.event.EventListenerList;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.NetDirection;
import org.openslx.bwlp.thrift.iface.NetRule;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.helper.GridManager;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.helper.TextChangeListener;

/**
 * Widget for advanced configuration options for lectures. This handles
 * following options - Network rules - Runscript - USB
 */
public class AdvancedConfigurator extends AdvancedConfiguratorLayout {

	private static final long serialVersionUID = -3497629601818983994L;
	private final static Logger LOGGER = Logger
			.getLogger(AdvancedConfigurator.class);
	private String originalRawRuleText = null;
	private String originalRunScript = null;
	/**
	 * Character defining how the rules are parsed, e.g. for whitespace \\s
	 * Example: "8.8.8.8 80 in" would be split in -hostname "8.8.8.8" -port "80"
	 * -direction "in"
	 */
	private static final String FIELD_DELIMITER = "\\s";

	public AdvancedConfigurator() {
		super();

		final TextChangeListener docListener = new TextChangeListener() {
			@Override
			public void changed() {
				fireAdvancedConfigurationChangeEvent(new AdvancedConfigurationChangeEvent(
						new Object()));
			}
		};
		tpNetworkRules.getDocument().addDocumentListener(docListener);
		taRunScript.getDocument().addDocumentListener(docListener);

	}

	public boolean hasChanged() {
		return !originalRawRuleText.equalsIgnoreCase(tpNetworkRules.getText())
				|| !originalRunScript.equalsIgnoreCase(taRunScript.getText());
	}

	/**
	 * Gets the state of the widget. This will first try to parse the
	 * tpNetworkRules and taRunScript and build the corresponding
	 * AdvancedConfiguration Object returned.
	 * 
	 * @return advanced configuration object composed of the parsed network
	 *         rules as List<NetRule> and the raw runscript text as String
	 * @see org.openslx.dozmod.gui.control.AdvancedConfigurator.AdvancedConfiguration
	 */
	public AdvancedConfiguration getState() {
		// cleanup the TextPane for network rules if needed
		String input = tpNetworkRules.getText().trim();
		List<NetRule> rules = parseNetRules(input);
		if (rules != null) {
			return new AdvancedConfiguration(rules, taRunScript.getText());
		}
		return null;
	}

	/**
	 * Sets the state of this widget to the given AdvancedConfiguration. Basicly
	 * this sets the content of the text areas to the corresponding network
	 * rules/runscript as given by the AdvancedConfiguration object
	 * 
	 * @param config
	 *            AdvancedConfiguration to set the state to
	 */
	public void setState(final AdvancedConfiguration config) {
		// setText() blanks the text area if null is given, so no null checks
		originalRawRuleText = decodeNetRulesToText(config.netRulesList);
		originalRunScript = config.runScriptText != null ? config.runScriptText
				: "";
		this.tpNetworkRules.setText(originalRawRuleText);
		this.taRunScript.setText(originalRunScript);
	}

	/**
	 * "Decodes" the given list of NetRule to a single String. This should be
	 * used to set the text in the TextPane for the network rules
	 * 
	 * @param netRulesList
	 *            list of NetRule to decode
	 * @return String representation of the list of rules
	 */
	public static String decodeNetRulesToText(final List<NetRule> netRulesList) {
		if (netRulesList == null || netRulesList.isEmpty())
			return "";

		String decodedRules = "";
		Iterator<NetRule> it = netRulesList.iterator();
		while (it.hasNext()) {
			String currentLine = "";
			NetRule currentRule = it.next();
			// simple test for validity (since this comes from the server it
			// should be correct anyways)
			if (currentRule.host.isEmpty() || currentRule.port > 65535) {
				LOGGER.error("Invalid rule! Ignoring: " + currentRule.host
						+ ":" + currentRule.port);
				continue;
			}
			currentLine += currentRule.host + " ";
			currentLine += currentRule.port + " ";
			currentLine += currentRule.direction.name();
			decodedRules += currentLine
					+ (it.hasNext() ? System.lineSeparator() : "");
		}
		return decodedRules;
	}

	/**
	 * Parsed the given rawNetRules String to a list of NetRule
	 * 
	 * @param rawNetRules
	 *            the raw text to be parsed
	 * @return list of valid net rules parsed from the given rawNetRules,
	 *         invalid ones are not included
	 * @throws Exception
	 *             when parsing fails
	 */
	public List<NetRule> parseNetRules(final String rawNetRules) {
		if (rawNetRules == null)
			return null;
		List<NetRule> rulesList = new ArrayList<NetRule>();
		if (rawNetRules.isEmpty()) {
			return rulesList;
		}
		// prune the text first
		String prunedRawNetRules = rawNetRules.replaceAll("(?m)^\\s*", "");
		prunedRawNetRules = prunedRawNetRules.replaceAll("(?m)\\s*$", "");

		// split it line by line
		List<String> netRules = Arrays.asList(prunedRawNetRules.split("["
				+ System.lineSeparator() + "]"));
		for (int i = 0; i < netRules.size(); i++) {
			final String ruleLine = netRules.get(i);
			if (ruleLine == null || ruleLine.isEmpty()) {
				netRules.remove(i);
				continue;
			}
			LOGGER.debug("Parsing rule: " + ruleLine);
			// split the fields and check if we have 3 as expected.
			String[] fields = ruleLine.split(FIELD_DELIMITER);
			if (fields.length != 3) {
				markText(ruleLine, Color.RED);
				// log numbers for fields independently...
				LOGGER.debug("Invalid number of fields! Expected 3, got: "
						+ fields.length);
				Gui.showMessageBox(
						"Ungültige Syntax: Nutzen Sie: <host> <port> [in|out]",
						MessageType.ERROR, LOGGER, null);
				break;
			}

			// start to check fields one by one from the last to the first ....
			// check net direction: accept either 'in' or 'out' (case
			// insensitive)
			// TODO support combined 'in/out' rules
			String ruleDirection = fields[2].toLowerCase();
			if (!ruleDirection.matches("^(\\bin\\b|\\bout\\b)$")) {
				markText(ruleLine, Color.RED);
				LOGGER.debug("Invalid net direction! Expected 'in' or out'. Got: "
						+ ruleDirection);
				Gui.showMessageBox(
						"Ungültige Richtung: nur 'in', 'out' erlaubt!",
						MessageType.ERROR, LOGGER, null);
				break;
			}
			// check port: accept if > -2 and < 65535
			int port = -2;
			try {
				port = Integer.parseInt(fields[1]);
			} catch (NumberFormatException e) {
				LOGGER.error("Could not parse '" + fields[1] + "' to an int.");
			}

			// TODO: port = -1 for all ports?
			if (port <= -2 || port > 65535) {
				markText(ruleLine, Color.RED);
				LOGGER.debug("Invalid port number! Got: " + port);
				Gui.showMessageBox(
						"Ungültiges Port! Muss zwischen 0 und 65536 sein.",
						MessageType.ERROR, LOGGER, null);
				break;
			}
			// check hostname: bit more to do here
			// for IPs and/or resolvable hostnames we make use of java.net's
			// InetAddress.getByName() method which checks the validity of
			// an IP-Address represented by a string or if the given hostname
			// is resolvable. If any of these happen, we have a valid hostname.
			// Non-resolvable hostnames are handled differently, see after the
			// try/catch-block
			InetAddress ruleHost = null;
			try {
				ruleHost = InetAddress.getByName(fields[0]);
			} catch (UnknownHostException e) {
				// might be good to see this exception in the log file
				// LOGGER.debug("Invalid hostname (java.net): ", e);
			}
			if (ruleHost == null) {
				// either invalid IP-Address or an invalid resolvable hostname
				// however it might also be a non-resolvable hostname that would
				// be
				// valid in the actual pool-rooms, so lets check its syntax
				// according to: http://tools.ietf.org/html/rfc1034#section-3.1
				LOGGER.debug("Invalid host/IP! Got: " + fields[0]);
				if (checkHostnameSimple(fields[0])) {
					markText(ruleLine, Color.ORANGE);
					if (!Gui.showMessageBox(
							"Konnte '"
									+ fields[0]
									+ "' nicht verifizieren. Wollen Sie es trotzdem verwenden?",
							MessageType.WARNING_RETRY, LOGGER, null)) {
						break;
					}
				} else {
					markText(ruleLine, Color.RED);
					continue;
				}
			} else {
				markText(ruleLine, Color.GREEN);
			}
			// valid, put it in the list
			rulesList.add(new NetRule(NetDirection.valueOf(fields[2]
					.toUpperCase()), fields[0], port));
		}
		if (netRules.size() == rulesList.size()) {
			// pruned rules were successfully parsed so they are valid: set the
			// textpane to it
			tpNetworkRules.setText(prunedRawNetRules);
			LOGGER.debug("Success");
			return rulesList;
		}
		return null;
	}

	/**
	 * Very simple hostname check for the given String. This will only check for
	 * some requirements of valid hostnames as stated in
	 * http://tools.ietf.org/html/rfc1034#section-3.1 To recap: max length of
	 * the whole hostname must be < 254 ASCII characters, all domain labels
	 * (between two dots) must be between 1 and 63 chars long and domain labels
	 * can only contain digits, letters and hyphen. (Note: we also accept
	 * forward slash to accept subnets!)
	 * 
	 * @param hostname
	 *            as String to check the syntax of
	 * @return
	 */
	private boolean checkHostnameSimple(final String hostname) {
		if (hostname.length() > 254) {
			Gui.showMessageBox("Hostname ist zu lang!", MessageType.ERROR,
					LOGGER, null);
			return false;
		}
		// split by '.' to get domain levels
		String[] domainLabels = hostname.split("\\.");
		for (String domainLabel : domainLabels) {
			if (domainLabel.length() > 63) {
				// fail since domain level should be max 63 chars
				Gui.showMessageBox("Domain-Ebene '" + domainLabel
						+ "' länger als 63 Zeichen!", MessageType.ERROR,
						LOGGER, null);
				return false;
			}
			// length is ok, check for invalid characters
			for (int i = 0; i < domainLabel.length(); i++) {
				Character c = Character.valueOf(domainLabel.charAt(i));
				// only accepts numbers, letters, hyphen
				// forward slash as a special case to accept subnets...
				if (!(Character.isDigit(c) || Character.isLetter(c)
						|| c.equals('-') || c.equals('/'))) {
					Gui.showMessageBox("Ungültiges Zeichen '" + c
							+ "' in hostname!", MessageType.ERROR, LOGGER, null);
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Searches the given txt within tpNetworkRules and changes its color to the
	 * given color
	 * 
	 * @param txt
	 *            text to search within the tpNetworkRules
	 * @param color
	 *            to set the given txt to
	 */
	// TODO still buggy with text colors: the marking works fine
	// but when trying to input new text, funny things happen
	private void markText(final String txt, final Color color) {
		SimpleAttributeSet set = new SimpleAttributeSet();
		StyleConstants.setForeground(set, color);
		StyleConstants.setBold(set, color == Color.red);
		StyledDocument doc = tpNetworkRules.getStyledDocument();
		try {
			for (int pos = 0; pos < doc.getLength() - txt.length() + 1; ++pos) {
				String current = doc.getText(pos, txt.length());
				if (current.endsWith(System.lineSeparator())) {
					current = current.substring(0, current.length() - 1);
				}
				if (current.equals(txt)) {
					doc.setCharacterAttributes(pos, txt.length(), set, true);
					break;
				}
			}
		} catch (BadLocationException e) {
			LOGGER.error(
					"Failed to set '" + txt + "' to color " + color.toString(),
					e);
		}
		// resetting the char attr to what they were before (buggy)

		tpNetworkRules.setStyledDocument(doc);
		StyleConstants.setForeground(set, Color.WHITE);
		StyleConstants.setBold(set, false);
		tpNetworkRules.setCharacterAttributes(set, true);
	}

	/**
	 * Wrapper class for the advanced configuration information needed since we
	 * need to return a single object from the runAndReturn routine. This class
	 * has two members: the list of NetRule(s) (as List<NetRule>) and the
	 * runScriptText (as String)
	 */
	public static class AdvancedConfiguration {
		public List<NetRule> netRulesList;
		public String runScriptText;

		public AdvancedConfiguration(List<NetRule> netRulesList,
				String runScriptText) {
			this.netRulesList = netRulesList;
			this.runScriptText = runScriptText;
		}
	}

	/**
	 * Custom event mechanism to detect changes to the user list (Mostly needed
	 * for the reactToChange() stuff in LectureDetailsWindow)
	 */
	protected EventListenerList listenerList = new EventListenerList();

	public class AdvancedConfigurationChangeEvent extends EventObject {

		private static final long serialVersionUID = -8779550754760035845L;

		public AdvancedConfigurationChangeEvent(Object source) {
			super(source);
		}
	}

	public interface AdvancedConfigurationChangeEventListener extends
			EventListener {
		public void stateChanged(AdvancedConfigurationChangeEvent event);
	}

	public void addAdvancedConfigurationChangeEventListener(
			AdvancedConfigurationChangeEventListener listener) {
		listenerList.add(AdvancedConfigurationChangeEventListener.class,
				listener);
	}

	public void removeAdvancedConfigurationChangeEventListener(
			AdvancedConfigurationChangeEventListener listener) {
		listenerList.remove(AdvancedConfigurationChangeEventListener.class,
				listener);
	}

	void fireAdvancedConfigurationChangeEvent(
			AdvancedConfigurationChangeEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		for (int i = 0; i < listeners.length; i++) {
			if (listeners[i] == AdvancedConfigurationChangeEventListener.class) {
				((AdvancedConfigurationChangeEventListener) listeners[i + 1])
						.stateChanged(evt);
			}
		}
	}
}

/**
 * Internal layout class for the advanced configurator (to keep it clean even
 * for widgets)
 */
class AdvancedConfiguratorLayout extends JPanel {

	private static final long serialVersionUID = 648729071828404053L;

	private final static String txtNetworkOptionsTitle = "Netzwerk Einstellungen";
	private final static String txtNetworkOptionsDesc = "Hier können Sie Firewall-Regeln festlegen mit folgendem Format (TODO)";
	private final static String txtNetworkRulesTitle = "Netzwerk-Regeln";
	private final static String txtRunScriptTitle = "Run-Skript";
	private final static String txtRunScriptDesc = "Ein hier abgelegtes Skript wird beim Start einer Windows-VM automatisch ausgeführt.";

	private final JPanel pnlNetworkOptions;
	private final JPanel pnlRunScript;
	protected final JTextPane tpNetworkRules;
	protected final JTextArea taRunScript;

	public AdvancedConfiguratorLayout() {

		GridManager grid = new GridManager(this, 1, true,
				new Insets(5, 5, 5, 5));

		// middle panel for network rules
		pnlNetworkOptions = new JPanel();
		GridManager gridNetworkOptions = new GridManager(pnlNetworkOptions, 1,
				true, new Insets(2, 2, 2, 2));
		pnlNetworkOptions.setBorder(BorderFactory
				.createTitledBorder(txtNetworkOptionsTitle));
		tpNetworkRules = new JTextPane();

		JScrollPane scpNetworkRules = new JScrollPane(tpNetworkRules,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		pnlNetworkOptions.setBorder(BorderFactory
				.createTitledBorder(txtNetworkRulesTitle));
		gridNetworkOptions
				.add(new WordWrapLabel(txtNetworkOptionsDesc, false, true))
				.fill(true, false).expand(true, false);
		gridNetworkOptions.nextRow();
		gridNetworkOptions.add(scpNetworkRules).fill(true, true)
				.expand(true, true);
		gridNetworkOptions.finish(false);

		// second middle panel for the run script textpane
		pnlRunScript = new JPanel();
		GridManager gridRunScript = new GridManager(pnlRunScript, 1, true,
				new Insets(2, 2, 2, 2));
		taRunScript = new JTextArea("", 5, 20);
		taRunScript.setLineWrap(true);
		taRunScript.setWrapStyleWord(true);
		JScrollPane scpRunScript = new JScrollPane(taRunScript,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		pnlRunScript.setBorder(BorderFactory
				.createTitledBorder(txtRunScriptTitle));
		gridRunScript.add(new WordWrapLabel(txtRunScriptDesc, false, true))
				.fill(true, false).expand(true, false);
		gridRunScript.nextRow();
		gridRunScript.add(scpRunScript).fill(true, true).expand(true, true);
		gridRunScript.finish(false);

		// build the final grid
		grid.add(pnlNetworkOptions).fill(true, true).expand(true, true);
		grid.nextRow();
		grid.add(pnlRunScript).fill(true, true).expand(true, true);
		grid.finish(false);
	}
}