From 57e40cabfe3ca78fbcaaf6bfa6385acf631977f5 Mon Sep 17 00:00:00 2001 From: "Barbara B. Frosik" <bfrosik@aps.anl.gov> Date: Wed, 5 Aug 2015 22:29:51 +0000 Subject: [PATCH] added source --- .../lib/jdatepicker-1.3.4.jar | Bin 0 -> 42949 bytes .../gov/anl/dm/esafsync/ExperimentList.java | 324 +++++++++++ .../src/gov/anl/dm/esafsync/Gui.java | 92 ++++ .../src/gov/anl/dm/esafsync/LoginWindow.java | 102 ++++ .../gov/anl/dm/esafsync/OracleConnection.java | 50 ++ .../gov/anl/dm/esafsync/PsqlConnection.java | 91 ++++ .../serviceconn/DaqServiceConnection.java | 30 + .../serviceconn/ServiceConnection.java | 513 ++++++++++++++++++ .../anl/dm/esafsync/serviceconn/Session.java | 131 +++++ .../serviceconn/StorageServiceConnection.java | 46 ++ 10 files changed, 1379 insertions(+) create mode 100644 tools/ExperimentSynchronizer/lib/jdatepicker-1.3.4.jar create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/ExperimentList.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/Gui.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/LoginWindow.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/OracleConnection.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/PsqlConnection.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/DaqServiceConnection.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/ServiceConnection.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/Session.java create mode 100644 tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/StorageServiceConnection.java diff --git a/tools/ExperimentSynchronizer/lib/jdatepicker-1.3.4.jar b/tools/ExperimentSynchronizer/lib/jdatepicker-1.3.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..a71625968ac3da67a3ec30b5d9f0e6fb344ca59e GIT binary patch literal 42949 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 literal 0 HcmV?d00001 diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/ExperimentList.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/ExperimentList.java new file mode 100644 index 00000000..c65538bd --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/ExperimentList.java @@ -0,0 +1,324 @@ +package gov.anl.dm.esafsync; + +import gov.anl.dm.esafsync.serviceconn.DaqServiceConnection; +import gov.anl.dm.esafsync.serviceconn.ServiceConnection; +import gov.anl.dm.esafsync.serviceconn.ServiceConnection.ServiceConnectionStatus; +import gov.anl.dm.esafsync.serviceconn.StorageServiceConnection; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFormattedTextField.AbstractFormatter; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; + +import org.jdatepicker.impl.JDatePanelImpl; +import org.jdatepicker.impl.JDatePickerImpl; +import org.jdatepicker.impl.UtilDateModel; + + +class ExperimentList extends JFrame +{ + private static final long serialVersionUID = 1L; + + public class DateLabelFormatter extends AbstractFormatter { + private static final long serialVersionUID = 1L; + private String datePattern = "MM-dd-yyyy"; + private SimpleDateFormat dateFormatter = new SimpleDateFormat(datePattern); + + @Override + public Object stringToValue(String text) throws ParseException { + return dateFormatter.parseObject(text); + } + + @Override + public String valueToString(Object value) throws ParseException { + if (value != null) { + Calendar cal = (Calendar) value; + return dateFormatter.format(cal.getTime()); + } + return ""; + } + + public String valueToString(Date value) throws ParseException { + if (value != null) { + Date cal = (Date) value; + return dateFormatter.format(cal.getTime()); + } + return ""; + } + + } + + enum Column { + NAME("Experiment name"), + DESCRIPTION("Description"), + START_DATE("Start Date"), + END_DATE("End Date"), + MANAGER("Manager(s)"), + PI("Principal Investigator(s)"), + USER("Users"); + + private final String column; + + private Column(String column) { + this.column = column; + } + + @Override + public String toString() { + return column; + } + } + + enum Role { + MANAGER("Manager"), + PI("Principal Investigator"), + USER("User"); + + private final String role; + + private Role(String role) { + this.role = role; + } + + @Override + public String toString() { + return role; + } + } + + class ExperimentTableModel extends AbstractTableModel { + private static final long serialVersionUID = 1L; + + String dataValues [][]; + + @Override + public int getRowCount() { + return dataValues.length; + } + + @Override + public int getColumnCount() { + return Column.values().length; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return dataValues[rowIndex][columnIndex]; + } + + public String getColumnName(int col) { + return Column.values()[col].toString(); + } + + public Class getColumnClass(int c) { + return getValueAt(0, c).getClass(); + } + + void setTable(String list) { + List<String[]> expTable = new ArrayList<>(); + String [] rows = list.split("EXP"); + for (int i=0; i<rows.length; i++) { + String [] cells = rows[i].split(","); + boolean goodRow = false; + int j = 0; + for (j = 0; j < cells[0].length(); j++) { + if ( Character.isDigit(cells[0].charAt(j)) ) { + goodRow = true; + break; + } + } + if (!goodRow) { + continue; + } else { + // parse experiment data + String[] experiment = new String[getColumnCount()+1]; + experiment[Column.NAME.ordinal()] = cells[0].substring(j); + experiment[Column.DESCRIPTION.ordinal()] = cells[1]; + experiment[Column.START_DATE.ordinal()] = cells[3]; + experiment[Column.END_DATE.ordinal()] = cells[4]; + experiment[Column.MANAGER.ordinal()] = parseUsers(cells[5]); + experiment[Column.PI.ordinal()] = parseUsers(cells[6]); + int lastIndex = cells[7].indexOf("\""); + experiment[Column.USER.ordinal()] = parseUsers(cells[7].substring(0, lastIndex)); + expTable.add(experiment); + } + } + dataValues = new String [expTable.size()][getColumnCount()]; + for (int i=0; i < expTable.size(); i++) { + for (int j=0; j < getColumnCount(); j++) { + dataValues[i][j] = expTable.get(i)[j]; + } + } + } + + private String parseUsers(String userList) { + if (userList.equals("00000")) { + return ""; + } + String parsedUsers = ""; + String [] users = userList.split(":"); + for (int i=0; i < users.length; i++) { + parsedUsers = parsedUsers + "d" + users[i] + " "; + } + return parsedUsers; + } + } + + StorageServiceConnection sconnection; + private JTable table = null; + private ExperimentTableModel tableModel = null; + private JScrollPane scrollPane; + + ExperimentList(String sector, OracleConnection oconnection, StorageServiceConnection sconnection, DaqServiceConnection dconnection) { + this.sconnection = sconnection; + setTitle("Experiment Import"); + setSize(1000, 500); + setBackground(Color.gray); + + JPanel topPanel = new JPanel(); + topPanel.setBorder(BorderFactory.createLoweredSoftBevelBorder()); + topPanel.setLayout( new BorderLayout() ); + getContentPane().add( topPanel ); + + JPanel entryPanel = new JPanel(new FlowLayout()); + topPanel.add(entryPanel, BorderLayout.NORTH); + + JLabel entryLabel = new JLabel("Enter date range of experiment start date"); + entryPanel.add(entryLabel); + + UtilDateModel startModel = new UtilDateModel(); + Properties p = new Properties(); + p.put("text.today", "Today"); + p.put("text.month", "Month"); + p.put("text.year", "Year"); + JDatePanelImpl startDatePanel = new JDatePanelImpl(startModel, p); + JDatePickerImpl startDatePicker = new JDatePickerImpl(startDatePanel, new DateLabelFormatter()); + entryPanel.add(startDatePicker); + + UtilDateModel endModel = new UtilDateModel(); + JDatePanelImpl endDatePanel = new JDatePanelImpl(endModel, p); + JDatePickerImpl endDatePicker = new JDatePickerImpl(endDatePanel, new DateLabelFormatter()); + entryPanel.add(endDatePicker); + + JButton submitDatesBtn = new JButton("click to get experiments list"); + entryPanel.add(submitDatesBtn); + + JPanel selectPanel = new JPanel(new FlowLayout()); + topPanel.add(selectPanel, BorderLayout.SOUTH); + + JButton submitSelectionBtn = new JButton("select experiments and click the button to import"); + selectPanel.add(submitSelectionBtn); + submitSelectionBtn.setVisible(false); + + JButton submitSelectionStartExpBtn = new JButton("select experiments and click the button to import and start"); + selectPanel.add(submitSelectionStartExpBtn); + submitSelectionStartExpBtn.setVisible(false); + + submitDatesBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + try { + entryLabel.setVisible(true); + submitDatesBtn.setVisible(true); + submitSelectionBtn.setVisible(true); + submitSelectionStartExpBtn.setVisible(true); + + DateLabelFormatter df = new DateLabelFormatter(); + Date startSelectedDate = (Date) startDatePicker.getModel().getValue(); + String start = df.valueToString(startSelectedDate); + Date endSelectedDate = (Date) endDatePicker.getModel().getValue(); + String end = df.valueToString(endSelectedDate); + String list = oconnection.getExperiments(sector, start, end); + if (table == null) { + tableModel = new ExperimentTableModel(); + tableModel.setTable(list); + table = new JTable(tableModel); + scrollPane = new JScrollPane( table ); + topPanel.add( scrollPane, BorderLayout.CENTER ); + table.setRowSelectionAllowed( true ); + pack(); + setVisible(true); + table.setVisible(true); + } else { + tableModel.setTable(list); + tableModel.fireTableStructureChanged(); + } + } catch (SQLException | ParseException e1) { + JOptionPane.showMessageDialog(null, e1.getMessage()); + } + } + }); + + submitSelectionBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + int[] selection = table.getSelectedRows(); + for (int i = 0; i < selection.length; i++) { + String experimentName = (String)table.getModel().getValueAt(selection[i], Column.NAME.ordinal()); + String description = (String)table.getModel().getValueAt(selection[i], Column.DESCRIPTION.ordinal()); + if (sconnection.addExperiment(experimentName, description) != ServiceConnection.ServiceConnectionStatus.SUCCESS) { + continue; + } + addRole((String)table.getModel().getValueAt(selection[i], Column.MANAGER.ordinal()), experimentName, Role.MANAGER); +// addRole((String)table.getModel().getValueAt(selection[i], Column.PI.ordinal()), experimentName, Role.PI); + addRole((String)table.getModel().getValueAt(selection[i], Column.USER.ordinal()), experimentName, Role.USER); + } + table.clearSelection(); + } + }); + + submitSelectionStartExpBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + int[] selection = table.getSelectedRows(); + if ((selection.length > 0) && (dconnection.init() == ServiceConnectionStatus.SUCCESS) && (dconnection.login() == ServiceConnectionStatus.SUCCESS)) { + for (int i = 0; i < selection.length; i++) { + String experimentName = (String)table.getModel().getValueAt(selection[i], Column.NAME.ordinal()); + String description = (String)table.getModel().getValueAt(selection[i], Column.DESCRIPTION.ordinal()); + if (sconnection.addExperiment(experimentName, description) != ServiceConnection.ServiceConnectionStatus.SUCCESS) { + continue; + } + addRole((String)table.getModel().getValueAt(selection[i], Column.MANAGER.ordinal()), experimentName, Role.MANAGER); + // addRole((String)table.getModel().getValueAt(selection[i], Column.PI.ordinal()), experimentName, Role.PI); + addRole((String)table.getModel().getValueAt(selection[i], Column.USER.ordinal()), experimentName, Role.USER); + if (sconnection.startExperiment(experimentName) == ServiceConnection.ServiceConnectionStatus.SUCCESS) { + dconnection.startDaq(experimentName, experimentName); + } + } + } + table.clearSelection(); + } + }); + } + + private void addRole(String userList, String experimentName, Role role) { + if ((userList == null) || userList.isEmpty()) { + return; + } + String [] users = userList.split(" "); + for (int i = 0; i < users.length; i++) { + sconnection.addExperimentUser(users[i], experimentName, role.toString()); + } + } + +} diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/Gui.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/Gui.java new file mode 100644 index 00000000..acfa7e43 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/Gui.java @@ -0,0 +1,92 @@ +package gov.anl.dm.esafsync; + +import gov.anl.dm.esafsync.serviceconn.DaqServiceConnection; +import gov.anl.dm.esafsync.serviceconn.ServiceConnection; +import gov.anl.dm.esafsync.serviceconn.StorageServiceConnection; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.Properties; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +class Gui +{ + public static void main(String arg[]) + { + + String configFile = arg[0]; + Properties configProperties = new Properties(); + setConfig(configFile, configProperties); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + new Gui(configProperties); + } + }); + } + + private Gui(Properties configProperties) { + OracleConnection oconnection = new OracleConnection(); + try { + oconnection.connect(configProperties); + } catch (SQLException e1) { + JOptionPane.showMessageDialog(null, e1.getMessage()); + System.exit(0); + } + StorageServiceConnection sconnection = new StorageServiceConnection(); + if (sconnection.init(configProperties.getProperty("dm.storageServ.connection")) != ServiceConnection.ServiceConnectionStatus.SUCCESS) { + System.exit(0); + } + DaqServiceConnection dconnection = new DaqServiceConnection(configProperties.getProperty("dm.daqServ.connection")); + try + { + LoginWindow frame = new LoginWindow(sconnection, oconnection, dconnection); + frame.setSize(300,200); + frame.setVisible(true); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + if (sconnection != null) { + sconnection.close(); + } + if (oconnection != null) { + oconnection.close(); + } + System.exit(0); + } + }); + } + catch(Exception e) { + JOptionPane.showMessageDialog(null, e.getMessage()); + } + } + + private static void setConfig(String config, Properties configProperties) { + InputStream configInputStream = null; + try { + configInputStream = new FileInputStream(config); + } catch (Exception e) { + JOptionPane.showMessageDialog(null, e.getMessage()); + System.exit(0); + } + + if (configInputStream != null) { + try { + configProperties.load(configInputStream); + } catch (IOException e1) { + JOptionPane.showMessageDialog(null, e1.getMessage()); + System.exit(0); + } + } else { + System.exit(-1); + } + } +} \ No newline at end of file diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/LoginWindow.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/LoginWindow.java new file mode 100644 index 00000000..0dd93486 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/LoginWindow.java @@ -0,0 +1,102 @@ +package gov.anl.dm.esafsync; + + + +import gov.anl.dm.esafsync.serviceconn.DaqServiceConnection; +import gov.anl.dm.esafsync.serviceconn.ServiceConnection; +import gov.anl.dm.esafsync.serviceconn.StorageServiceConnection; + +import javax.swing.*; + +import java.awt.event.*; + +public final class LoginWindow extends JFrame { + private static final long serialVersionUID = 1L; + + StorageServiceConnection sconnection; + OracleConnection oconnection; + JPanel panel; + + LoginWindow(StorageServiceConnection sconnection, OracleConnection oconnection, DaqServiceConnection dconnection) { + this.sconnection = sconnection; + this.oconnection = oconnection; + + setTitle("Experiment Import Login"); + JPanel panel = new JPanel(); + add(panel); + panel.setLayout(null); + + JLabel userLabel = new JLabel("User"); + userLabel.setBounds(10, 10, 80, 25); + panel.add(userLabel); + + JTextField userText = new JTextField(20); + userText.setBounds(100, 10, 160, 25); + panel.add(userText); + + JLabel passwordLabel = new JLabel("Password"); + passwordLabel.setBounds(10, 40, 80, 25); + panel.add(passwordLabel); + + JPasswordField passwordText = new JPasswordField(20); + passwordText.setBounds(100, 40, 160, 25); + panel.add(passwordText); + + JLabel sectorLabel = new JLabel("Sector"); + sectorLabel.setBounds(10, 70, 80, 25); + panel.add(sectorLabel); + + JTextField sectorText = new JTextField(20); + sectorText.setBounds(100, 70, 160, 25); + panel.add(sectorText); + + JButton submitButton = new JButton("submit"); + submitButton.setBounds(10, 120, 80, 25); + panel.add(submitButton); + + submitButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String user = userText.getText().trim(); + String pass = new String(passwordText.getPassword()); + if ((user == null) || (user.isEmpty()) || (pass == null) || (pass.isEmpty())) { + JOptionPane.showMessageDialog(null,"enter login and password", + "Error",JOptionPane.ERROR_MESSAGE); + } else { + sconnection.setLogin(user, pass); + dconnection.setLogin(user, pass); + int aaResult = sconnection.login(); + switch (aaResult) { + case ServiceConnection.ServiceConnectionStatus.SUCCESS: + setVisible(false); + ExperimentList page=new ExperimentList(sectorText.getText().trim(), oconnection, sconnection, dconnection); + page.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + page.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + sconnection.close(); + oconnection.close(); + System.exit(0); + } + }); + page.setVisible(true); + break; + case ServiceConnection.ServiceConnectionStatus.AUTHORIZATION_ERROR: + userText.setText(""); + passwordText.setText(""); + JOptionPane.showMessageDialog(null,"not authorized user", + "Error",JOptionPane.ERROR_MESSAGE); + break; + case ServiceConnection.ServiceConnectionStatus.AUTHENTICATION_ERROR: + userText.setText(""); + passwordText.setText(""); + JOptionPane.showMessageDialog(null,"Incorrect login or password", + "Error",JOptionPane.ERROR_MESSAGE); + break; + default: + // error message generated from exception + } + } + } + }); + } +} \ No newline at end of file diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/OracleConnection.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/OracleConnection.java new file mode 100644 index 00000000..f91bfe76 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/OracleConnection.java @@ -0,0 +1,50 @@ +package gov.anl.dm.esafsync; + +import java.sql.CallableStatement; +import java.sql.DriverManager; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.Properties; + +public class OracleConnection { + Connection connection = null; + Statement statement; + + void connect(Properties config) throws SQLException { + DriverManager.registerDriver(new oracle.jdbc.OracleDriver()); + connection = DriverManager.getConnection( + config.getProperty("oracle.database.connection"), + config.getProperty("oracle.database.username"), + config.getProperty("oracle.database.password")); + } + + String getExperiments(String sector, String lower, String upper) throws SQLException { + CallableStatement callableStatement = + connection.prepareCall("{? = call safety.esaf_exp0001.get_exps(?,?,?,?)}"); + + callableStatement.registerOutParameter(1, Types.VARCHAR); + callableStatement.setObject(2, ""); + callableStatement.setString(3, sector); + callableStatement.setString(4, lower); + callableStatement.setString(5, upper); + + callableStatement.execute(); + + return callableStatement.getString(1); + } + + void close() { + try { + if (statement != null) { + statement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException ex) { +// nothing to do here + } + } +} diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/PsqlConnection.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/PsqlConnection.java new file mode 100644 index 00000000..cdfc3889 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/PsqlConnection.java @@ -0,0 +1,91 @@ +package gov.anl.dm.esafsync; + +import java.sql.DriverManager; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +public class PsqlConnection { + public final class UserInfo { + private final String signature; + private final int id; + + UserInfo(String signature, int id) { + this.signature = signature; + this.id = id; + } + String getSignature() { + return signature; + } + + int getId() { + return id; + } + } + + final int ADMIN_ROLE_ID = 1; + Connection connection = null; + + public void connect(Properties config) throws SQLException, ClassNotFoundException { + Class.forName("org.postgresql.Driver"); + connection = DriverManager.getConnection( + config.getProperty("dm.database.connection"), + config.getProperty("dm.database.username"), + config.getProperty("dm.database.password")); + } + + UserInfo getUserInfo(String user) throws SQLException { + String signature = null; + int id = -1; + Statement statement = connection.createStatement(); + if (statement != null) { + ResultSet results = statement.executeQuery("SELECT * FROM user_info WHERE username = '" + user + "';"); + if (results != null) { + while (results.next()) { + signature = results.getString("password"); + if (signature.length() > 0) { + id = Integer.parseInt(results.getString("id")); + break; + } + } + results.close(); + } + statement.close(); + } + if (id < 0) { + return null; + } else { + return new UserInfo(signature, id); + } + } + + boolean isAuthorized(int id) throws SQLException { + boolean isAuthorized = false; + Statement statement = connection.createStatement(); + if (statement != null) { + ResultSet results = statement.executeQuery("SELECT * FROM user_system_role WHERE user_id = " + id + ";"); + if (results != null) { + while (results.next()) { + int roleId = Integer.parseInt(results.getString("role_type_id")); + if (roleId == ADMIN_ROLE_ID) { + isAuthorized = true; + break; + } + } + results.close(); + } + statement.close(); + } + return isAuthorized; + } + + void close() { + try { + connection.close(); + } catch (SQLException ex) { + // nothing to do here + } + } +} diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/DaqServiceConnection.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/DaqServiceConnection.java new file mode 100644 index 00000000..1975ee37 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/DaqServiceConnection.java @@ -0,0 +1,30 @@ +package gov.anl.dm.esafsync.serviceconn; + +import java.util.HashMap; +import java.util.Map; + +public class DaqServiceConnection extends ServiceConnection{ + + class Keyword { + static final String DAQ_EXPERIMENT_NAME = "experimentName"; + static final String DAQ_DATA_DIRECTORY = "dataDirectory"; + } + + private final String url; + + public DaqServiceConnection(String url) { + this.url = url; + } + + public final int init() { + return super.init(url); + } + + public void startDaq(String experimentName, String directory) { + Map<String, String> data = new HashMap<>(); + data.put(Keyword.DAQ_EXPERIMENT_NAME, experimentName); + data.put(Keyword.DAQ_DATA_DIRECTORY, directory); + invokeSessionPostRequest(StorageServUrl.EXPERIMENT, data); + } + +} diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/ServiceConnection.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/ServiceConnection.java new file mode 100644 index 00000000..bb6d0d15 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/ServiceConnection.java @@ -0,0 +1,513 @@ +package gov.anl.dm.esafsync.serviceconn; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JOptionPane; + +public class ServiceConnection { + + class DmHttpHeaders { + static final String DM_SET_COOKIE_HEADER = "Set-Cookie"; + final static String DM_SESSION_ROLE_HTTP_HEADER = "Dm-Session-Role"; + final static String DM_STATUS_CODE_HTTP_HEADER = "Dm-Status-Code"; + final static String DM_STATUS_MESSAGE_HTTP_HEADER = "Dm-Status-Message"; + final static String DM_EXCEPTION_TYPE_HTTP_HEADER = "Dm-Exception-Type"; + } + + class DmHttpStatus { + static final int DM_HTTP_OK = 200; + static final int DM_HTTP_UNAUTHORIZED = 401; + static final int DM_HTTP_INTERNAL_ERROR = 500; + } + + class StorageServUrl { + static final String LOGIN_REQUEST = "/login"; + static final String EXPERIMENT = "/experiments"; + static final String EXPERIMENT_USER = "/usersByExperiment"; + static final String START_EXPERIMENT = "/experiments/start"; + static final String START_DAQ = "/experiments/startDaq"; + } + + class DmStatus { + static final int DM_OK = 0; + static final int DM_ERROR = 1; + static final int DM_INTERNAL_ERROR = 2; + static final int DM_COMMUNICATION_ERROR = 3; + static final int DM_CONFIGURATION_ERROR = 4; + static final int DM_AUTHORIZATION_ERROR = 5; + static final int DM_AUTHENTICATION_ERROR = 6; + static final int DM_DB_ERROR = 7; + static final int DM_URL_ERROR = 8; + static final int DM_TIMEOUT = 9; + static final int DM_INVALID_ARGUMENT = 10; + static final int DM_INVALID_REQUEST = 11; + static final int DM_INVALID_SESSION = 12; + static final int DM_COMMAND_FAILED =13; + static final int DM_OBJECT_NOT_FOUND = 14; + static final int DM_OBJECT_ALREADY_EXISTS = 15; + static final int DM_INVALID_OBJECT_STATE = 16; + } + + class Keyword { + static final String USERNAME = "username"; + static final String PASSWORD = "password"; + } + + public class ServiceConnectionStatus { + public static final int SUCCESS = 0; + public static final int AUTHENTICATION_ERROR = -2; + public static final int AUTHORIZATION_ERROR = -3; + public static final int INVALID_OPERATION = -4; + public static final int CONNECTION_ERROR = -5; + public static final int PROTOCOL_ERROR = -6; + public static final int ERROR = -1; + } + + private final class Login { + private String username; + private String password; + + private Login(String username, String password) { + this.username = username; + this.password = password; + } + private void clear() { + this.username = null; + this.password = null; + } + private int login() { + int result = ServiceConnection.this.login(username, password); + clear(); + return result; + } + } + + static final String ESAF_EXPERIMENT_TYPE = "1"; + + protected URL serviceUrl; + private Session session = new Session(); + private Login login; + + public final int init(String serviceUrl) { + if (serviceUrl == null) { + JOptionPane.showMessageDialog(null,"service url is not specified.", "Error",JOptionPane.ERROR_MESSAGE); + return ServiceConnectionStatus.ERROR; + } + try { + this.serviceUrl = new URL(serviceUrl); + } catch (MalformedURLException ex) { + JOptionPane.showMessageDialog(null,"Malformed service url: " + serviceUrl, "Error",JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, ex.getMessage()); + return ServiceConnectionStatus.ERROR; + } + + String protocol = this.serviceUrl.getProtocol(); + System.out.println("protocol "+ protocol); + if (protocol == null) { + JOptionPane.showMessageDialog(null,"Unsupported service protocol specified in " + serviceUrl, "Error",JOptionPane.ERROR_MESSAGE); + return ServiceConnectionStatus.ERROR; + } + return ServiceConnectionStatus.SUCCESS; + } + + private URL getServiceUrl() { + return serviceUrl; + } + + private int checkHttpResponse(HttpURLConnection connection) { + int code = 0; + String exceptionType = connection.getHeaderField(DmHttpHeaders.DM_EXCEPTION_TYPE_HTTP_HEADER); + if (exceptionType != null) { + String statusMessage = connection.getHeaderField(DmHttpHeaders.DM_STATUS_MESSAGE_HTTP_HEADER); + String statusCode = connection.getHeaderField(DmHttpHeaders.DM_STATUS_CODE_HTTP_HEADER); + code = Integer.parseInt(statusCode); + JOptionPane.showMessageDialog(null,exceptionType +" "+ code +" "+ statusMessage, "Error",JOptionPane.ERROR_MESSAGE); + } + switch (code) { + case DmStatus.DM_OK : + return ServiceConnectionStatus.SUCCESS; + case DmStatus.DM_AUTHENTICATION_ERROR: + return ServiceConnectionStatus.AUTHENTICATION_ERROR; + case DmStatus.DM_AUTHORIZATION_ERROR: + return ServiceConnectionStatus.AUTHORIZATION_ERROR; + case DmStatus.DM_COMMUNICATION_ERROR: + case DmStatus.DM_CONFIGURATION_ERROR: + case DmStatus.DM_ERROR: + case DmStatus.DM_INVALID_SESSION: + case DmStatus.DM_TIMEOUT: + case DmStatus.DM_URL_ERROR: + return ServiceConnectionStatus.ERROR; + default: + return ServiceConnectionStatus.INVALID_OPERATION; + } + } + + private String getFullRequestUrl(String requestUrl) { + String url = serviceUrl + requestUrl; + return url; + } + + private static String preparePostData(Map<String, String> data) { + try { + String postData = ""; + String separator = ""; + for (String key : data.keySet()) { + postData += separator + key + "=" + URLEncoder.encode(data.get(key), "UTF8"); + separator = "&"; + } + return postData; + } catch (UnsupportedEncodingException ex) { + JOptionPane.showMessageDialog(null, ex.getMessage()); + return null; + } + } + + private void updateSessionCookie(HttpURLConnection connection) { + String cookie = connection.getHeaderField(DmHttpHeaders.DM_SET_COOKIE_HEADER); + if (cookie != null) { + session.setCookie(cookie); + } + String sessionRole = connection.getHeaderField(DmHttpHeaders.DM_SESSION_ROLE_HTTP_HEADER); + if (sessionRole != null) { + session.setRole(Session.Role.fromString(sessionRole)); + } + } + + private static int sendPostData(Map<String, String> data, HttpURLConnection connection) { + String postData = preparePostData(data); + try (DataOutputStream dos = new DataOutputStream(connection.getOutputStream())) { + dos.writeBytes(postData); + dos.flush(); + return ServiceConnectionStatus.SUCCESS; + } catch (IOException ex) { + JOptionPane.showMessageDialog(null, ex.getMessage()); + JOptionPane.showMessageDialog(null, "misconfigured service or closed ", "Error",JOptionPane.ERROR_MESSAGE); + return ServiceConnectionStatus.CONNECTION_ERROR; + } + } + + private static String readHttpResponse(HttpURLConnection connection) { + try { + BufferedReader br = new BufferedReader(new InputStreamReader( + (connection.getInputStream()))); + StringBuilder sb = new StringBuilder(); + String output; + while ((output = br.readLine()) != null) { + sb.append(output); + sb.append('\n'); + } + return sb.toString(); + } catch (IOException ex) { + JOptionPane.showMessageDialog(null, ex.getMessage()); + return null; + } + } + + private static void setCookieRequestHeader(HttpURLConnection connection, String sessionCookie) { + if (sessionCookie != null) { + connection.setRequestProperty("Cookie", sessionCookie); + } + } + + private static int setPostRequestHeaders(HttpURLConnection connection, String sessionCookie) { + int resp = setPostRequestHeaders(connection); + if (resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + setCookieRequestHeader(connection, sessionCookie); + return resp; + } + + private static int setPostRequestHeaders(HttpURLConnection connection) { + try { + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + return ServiceConnectionStatus.SUCCESS; + } catch (ProtocolException ex) { + JOptionPane.showMessageDialog(null, ex.getMessage()); + return ServiceConnectionStatus.PROTOCOL_ERROR; + } + } + + protected int invokeSessionPostRequest(String requestUrl, Map<String, String> data) { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + String sessionCookie = session.verifyCookie(); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + + int resp = setPostRequestHeaders(connection, sessionCookie); + if ( resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + resp = sendPostData(data, connection); + if ( resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + updateSessionCookie(connection); + resp = checkHttpResponse(connection); + readHttpResponse(connection); + return resp; + } catch (Exception ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + JOptionPane.showMessageDialog(null,errorMsg, "Error",JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, ex.getMessage()); + return ServiceConnectionStatus.ERROR; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + private static int setPutRequestHeaders(HttpURLConnection connection, String sessionCookie) { + int resp = setPutRequestHeaders(connection); + if ( resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + setCookieRequestHeader(connection, sessionCookie); + return resp; + } + + private static int setPutRequestHeaders(HttpURLConnection connection) { + try { + connection.setDoOutput(true); + connection.setRequestMethod("PUT"); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + return ServiceConnectionStatus.SUCCESS; + } catch (ProtocolException ex) { + JOptionPane.showMessageDialog(null, ex.getMessage()); + return ServiceConnectionStatus.PROTOCOL_ERROR; + } + } + + protected int invokeSessionPutRequest(String requestUrl, Map<String, String> data) { + String urlString = getFullRequestUrl(requestUrl); + HttpURLConnection connection = null; + try { + String sessionCookie = session.verifyCookie(); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + + int resp = setPutRequestHeaders(connection, sessionCookie); + if ( resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + resp = sendPostData(data, connection); + if ( resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + updateSessionCookie(connection); + resp = checkHttpResponse(connection); + readHttpResponse(connection); + return resp; + } catch (Exception ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + JOptionPane.showMessageDialog(null,errorMsg, "Error",JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, ex.getMessage()); + return ServiceConnectionStatus.ERROR; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + protected String encode(String s) { + byte[] encoded1 = Base64.getEncoder().encode(s.getBytes()); + String encoded2 = Base64.getEncoder().encodeToString(encoded1); + return encoded2; + } + +// private String verifySessionCookie() { +// return session.verifyCookie(); +// } +// +// private static String getResponseHeaders(HttpURLConnection connection) { +// String headerString = ""; +// Map<String, List<String>> headerMap = connection.getHeaderFields(); +// for (String key : headerMap.keySet()) { +// List<String> values = headerMap.get(key); +// headerString += key + ": " + values + "\n"; +// } +// return headerString; +// } +// +// private String invokeSessionGetRequest(String requestUrl) { +// String urlString = getFullRequestUrl(requestUrl); +// HttpURLConnection connection = null; +// try { +// String sessionCookie = session.verifyCookie(); +// URL url = new URL(urlString); +// connection = (HttpURLConnection) url.openConnection(); +// +// setGetRequestHeaders(connection, sessionCookie); +// updateSessionCookie(connection); +// checkHttpResponse(connection); +// return readHttpResponse(connection); +// } catch (Exception ex) { +// String errorMsg = "Cannot connect to " + getServiceUrl(); +// JOptionPane.showMessageDialog(null,errorMsg, "Error",JOptionPane.ERROR_MESSAGE); +// JOptionPane.showMessageDialog(null, ex.getMessage()); +// return null; +// } finally { +// if (connection != null) { +// connection.disconnect(); +// } +// } +// } +// +// private String invokeGetRequest(String requestUrl) { +// String urlString = getFullRequestUrl(requestUrl); +// HttpURLConnection connection = null; +// try { +// URL url = new URL(urlString); +// connection = (HttpURLConnection) url.openConnection(); +// updateSessionCookie(connection); +// checkHttpResponse(connection); +// return readHttpResponse(connection); +// } catch (Exception ex) { +// String errorMsg = "Cannot connect to " + getServiceUrl(); +// JOptionPane.showMessageDialog(null,errorMsg, "Error",JOptionPane.ERROR_MESSAGE); +// JOptionPane.showMessageDialog(null, ex.getMessage()); +// return null; +// } finally { +// if (connection != null) { +// connection.disconnect(); +// } +// } +// } +// +// private String invokeSessionDeleteRequest(String requestUrl) { +// String urlString = getFullRequestUrl(requestUrl); +// HttpURLConnection connection = null; +// try { +// String sessionCookie = session.verifyCookie(); +// URL url = new URL(urlString); +// connection = (HttpURLConnection) url.openConnection(); +// +// setDeleteRequestHeaders(connection, sessionCookie); +// updateSessionCookie(connection); +// checkHttpResponse(connection); +// return readHttpResponse(connection); +// } catch (Exception ex) { +// String errorMsg = "Cannot connect to " + getServiceUrl(); +// JOptionPane.showMessageDialog(null,errorMsg, "Error",JOptionPane.ERROR_MESSAGE); +// JOptionPane.showMessageDialog(null, ex.getMessage()); +// return null; +// } finally { +// if (connection != null) { +// connection.disconnect(); +// } +// } +// } +// +// private static void setGetRequestHeaders(HttpURLConnection connection, String sessionCookie) { +// setGetRequestHeaders(connection); +// setCookieRequestHeader(connection, sessionCookie); +// } +// +// private static void setGetRequestHeaders(HttpURLConnection connection) { +// try { +// connection.setRequestMethod("GET"); +// } catch (ProtocolException ex) { +// JOptionPane.showMessageDialog(null, ex.getMessage()); +// } +// } +// +// private static void setDeleteRequestHeaders(HttpURLConnection connection, String sessionCookie) { +// setDeleteRequestHeaders(connection); +// setCookieRequestHeader(connection, sessionCookie); +// } +// +// private static void setDeleteRequestHeaders(HttpURLConnection connection) { +// try { +// connection.setRequestMethod("DELETE"); +// } catch (ProtocolException ex) { +// JOptionPane.showMessageDialog(null, ex.getMessage()); +// } +// } +// +// +// private void getData(HttpURLConnection connection) { +// BufferedReader in; +// try { +// in = new BufferedReader( +// new InputStreamReader(connection.getInputStream())); +// String inputLine; +// StringBuffer response = new StringBuffer(); +// +// while ((inputLine = in.readLine()) != null) { +// response.append(inputLine); +// System.out.println(inputLine); +// } +// } catch (IOException e) { +// JOptionPane.showMessageDialog(null, e.getMessage()); +// } +// } + + public final void setLogin(String username, String password) { + this.login = new Login(username, password); + } + + public final int login() { + return login.login(); + } + + private final int login(String username, String password) { + HttpURLConnection connection = null; + try { + String urlString = getFullRequestUrl(StorageServUrl.LOGIN_REQUEST); + URL url = new URL(urlString); + connection = (HttpURLConnection) url.openConnection(); + setPostRequestHeaders(connection); + HashMap<String, String> loginData = new HashMap<>(); + loginData.put(Keyword.USERNAME, username); + loginData.put(Keyword.PASSWORD, password); + + int resp = sendPostData(loginData, connection); + if (resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + resp = checkHttpResponse(connection); + if (resp != ServiceConnectionStatus.SUCCESS) { + return resp; + } + session.setUsername(username); +// session.setId(sessionId); + updateSessionCookie(connection); + return ServiceConnectionStatus.SUCCESS; + } catch (ConnectException ex) { + String errorMsg = "Cannot connect to " + getServiceUrl(); + JOptionPane.showMessageDialog(null,errorMsg, "Error",JOptionPane.ERROR_MESSAGE); + return ServiceConnectionStatus.ERROR; + } catch (Exception ex) { + JOptionPane.showMessageDialog(null, ex.getMessage()); + return ServiceConnectionStatus.ERROR; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + public void close() { + session = null; + } + +} diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/Session.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/Session.java new file mode 100644 index 00000000..2b72e2c3 --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/Session.java @@ -0,0 +1,131 @@ +package gov.anl.dm.esafsync.serviceconn; + +import java.io.Serializable; +import java.net.HttpCookie; + +import javax.swing.JOptionPane; + +/** + * CDB session class, used for keeping all session-related information (session + * id, username, role, etc.). + */ +public class Session implements Serializable { + + public enum Role { + + USER("User"), + ADMIN("Administrator"); + + private final String type; + + private Role(String type) { + this.type = type; + } + + @Override + public String toString() { + return type; + } + + public static Role fromString(String type) { + Role role = null; + switch (type) { + case "User": + role = USER; + break; + case "Administrator": + role = ADMIN; + break; + } + return role; + } + } + + private static final long serialVersionUID = 1L; + + private String id = null; + private String username = null; + private String cookie = null; + + private Role role = null; + + public Session() { + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCookie() { + return cookie; + } + + public void setCookie(String cookie) { + this.cookie = cookie; + } + + public String verifyCookie() { + if (cookie == null) { + JOptionPane.showMessageDialog(null, "Valid session has not been established.", "Info",JOptionPane.INFORMATION_MESSAGE); + } else { + HttpCookie httpCookie = HttpCookie.parse(cookie).get(0); + if (httpCookie.hasExpired()) { + JOptionPane.showMessageDialog(null, "Session has expired.", "Info",JOptionPane.INFORMATION_MESSAGE); + } + } + return cookie; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } + + public boolean isAdminRole() { + if (role != null) { + return role.equals(Role.ADMIN); + } + return false; + } + + public boolean isUserRole() { + if (role != null) { + return role.equals(Role.USER); + } + return false; + } + + @Override + public String toString() { + String result = "{ "; + String delimiter = ""; + if (username != null) { + result += "username :" + username; + delimiter = "; "; + } + if (id != null) { + result += delimiter + "id : " + id; + delimiter = "; "; + } + if (cookie != null) { + result += delimiter + "cookie : " + cookie; + } + result += " }"; + return result; + } +} diff --git a/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/StorageServiceConnection.java b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/StorageServiceConnection.java new file mode 100644 index 00000000..915bae4f --- /dev/null +++ b/tools/ExperimentSynchronizer/src/gov/anl/dm/esafsync/serviceconn/StorageServiceConnection.java @@ -0,0 +1,46 @@ +package gov.anl.dm.esafsync.serviceconn; + +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JOptionPane; + +public class StorageServiceConnection extends ServiceConnection{ + + class Keyword { + static final String EXPERIMENT_NAME = "name"; + static final String EXPERIMENT_DESCRIPTION = "description"; + static final String EXPERIMENT_TYPE_ID = "experimentTypeId"; + } + + public int addExperiment(String name, String description) { + Map<String, String> data = new HashMap<>(); + if (name == null) { + JOptionPane.showMessageDialog(null, "The experiment name is null", "Error",JOptionPane.ERROR_MESSAGE); + return ServiceConnection.ServiceConnectionStatus.ERROR; + } else { + data.put(Keyword.EXPERIMENT_NAME, encode(name)); + } + if (description != null) { + data.put(Keyword.EXPERIMENT_DESCRIPTION, encode(description)); + } + data.put(Keyword.EXPERIMENT_TYPE_ID, ESAF_EXPERIMENT_TYPE); + return invokeSessionPostRequest(StorageServUrl.EXPERIMENT, data); + } + + public int addExperimentUser(String userName, String experimentName, String role) { + Map<String, String> data = new HashMap<>(); + return invokeSessionPostRequest(StorageServUrl.EXPERIMENT_USER + "/" + userName + "/" + experimentName + "/" + role, data); + } + + public int startExperiment(String name) { + Map<String, String> data = new HashMap<>(); + if (name == null) { + JOptionPane.showMessageDialog(null, "The experiment name is null", "Error",JOptionPane.ERROR_MESSAGE); + return ServiceConnection.ServiceConnectionStatus.ERROR; + } else { + data.put(Keyword.EXPERIMENT_NAME, encode(name)); + } + return invokeSessionPutRequest(StorageServUrl.START_EXPERIMENT, data); + } +} -- GitLab