miércoles, 20 de enero de 2021

37. My Wonderful Web Framework. (2) Actions. Defining the messagingsocket server

1. Introduction

There are 2 servers:

  1. Web server that makes the request to the socket server
  2. The Socket server that receives the request and executes a shell program
The shell program may include a shell script or it can execute a java program. The call to this programs is similar:


2. The SocketAction class in the webserver


This class inherits from BaseExecute that defines the base facilities.

It has a method "execute" that receives the fix and form params from the "application.yaml" file that is in the IFXX java project, for instance:

fixParams: '[STRING:JAVA_PATH -jar JARS_FOLDER/IF/MiJar.jar AYTOSOPERATION]', formParams: '{name: s_n, type: STRING, length: 1, value: S, required: true }'

In addition, it constructs a message to send to the socket server adding these additional parameters to the fix and form parameters:
  1. INET address of the user
  2. user name
  3. user-agent (Mozilla, Chrome..)
  4. Session-Id (HTTP)
Then the socket server IO and port is got from the properties: "var server".ip and "var server".port from the "app.properties" file.

The "var server" is an attribute of the action. By default, the value is "socket". This value is set in the application.yaml file when defining an action

server: socket,

The logic of sending and receiving messages with the server is defined in the procedure sendMessageCommand :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
	private void sendMessageCommand(String msg) throws IOException {
    	//System.out.println("Sending to server:"+ msg);
		String fileName1=null;
		byte[] bytes=null;
		String fileName1Key="(DOWNLOADER-FILENAME)";
		String bytesKey="(DOWNLOADER-BYTES)";
		String nRecords="(N.RECORDS=)";
    	out.println(msg);
    	
        String line = in.readLine().trim();
        
        //ERROR:* means that an error has occurred 
        //FIN:* means the end of the process
        while (! line.toUpperCase().startsWith("ERROR") &&
        	   ! line.toUpperCase().startsWith("FIN:")) { 
        	
        	if (line.contains(fileName1Key)) {
        		int pos=line.indexOf(fileName1Key);
        		fileName1=line.substring(pos+fileName1Key.length());
        	} else if (line.contains(bytesKey)) {
        		int pos=line.indexOf(bytesKey);
        		bytes=Base64.getDecoder().decode(line.substring(pos+bytesKey.length()));
        		bytes=ZipUtils.decompress(bytes);
        		addDownloaderEdu(fileName1, bytes);
        	} else if (line.contains(nRecords)) {
        		aS[2]=line.substring(line.indexOf(nRecords)+nRecords.length());
        		showMessage(line);
        	}
        	else showMessage(line, line.contains("ERROR"));
        	line = in.readLine();
        	//getAfectedRecords(line);
        }
        if (line.toUpperCase().startsWith("ERROR")) showError(line);
        else showMessage(line, line.contains("ERROR"));
    }

We can see that the condition for finishing the communication is that the message line starts with "ERROR" or "FIN:" . The first key indicates that an error has occurred, and the second one that the end of the process has been reached.

there are other keys that can contain the line message:
  1. (DOWNLOADER-FILENAME) and (DOWNLOADER-BYTES): To create a component called "DownloaderEdu" that downloads a file whose name is after the first key and the byte array to download is after the second key. NOTE that the byte arrays are compressed with ZIP and transformed to Base64. 
  2. (N.RECORDS=) the number of affected records by the process is sent after this key

3. The Socket server

There are 3 important files:
  • app.properties:  defines port to listen and paths to java and shells scripts
  • SimpleSocketServer class: the class with the main method to be executed once and manage the requests
  • ServerRequestHandler: Manages the treatment of the requests.
Here is the code:

app.properties:

1
2
3
4
5
#---location of shells scripts and java programs to be executed as external options
socket.port = 6666
java.path = /home/USER/MyPrograms/jdk-15.0.1/bin/java
jars.folder = /home/OTHERUSER/MyJars
shells.folder = /home/OTHERUSER/MyShells

SimpleSocketServer.java:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package u.requests.sockets;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;

import utils.PropertiesUtils;

/**
 * Only open port for serving messages
 * The important part is the access to ServerRequestHandler03 that manages all the logic
 * @author ximo
 *
 */
public class SimpleSocketServer extends Thread
{
    private boolean isExecutedFromJar=false;  //????? Cambiar al compilar
	private ServerSocket serverSocket;
    private int port;
    private boolean running = false;
    //private String propsFileName="properties"+File.separator+"app.properties";
    private Properties props=null;

    public SimpleSocketServer(){
    	port=6666;
    	try {
			props=PropertiesUtils.getProperties(isExecutedFromJar,"app");
			port=Integer.parseInt(props.getProperty("socket.port",""+port));
		} catch (Exception e) {e.printStackTrace();}
    }
    
    public void startServer() {
        try {
            serverSocket = new ServerSocket( port );
            this.start();
        } catch (IOException e) { e.printStackTrace(); }
    }

    public void stopServer() {
        running = false;
        this.interrupt();
    }

    @Override
    public void run() {
        running = true;
        //This is for accepting multiple connections
        while( running ) {
            try {
                System.out.println( "System.out.println-->Listening for a connection" );

                // Call accept() to receive the next connection
                Socket socket = serverSocket.accept();

                // Pass the socket to the RequestHandler thread for processing
                ServerRequestHandler requestHandler = new ServerRequestHandler(socket, props );
                requestHandler.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main( String[] args ) {
    	SimpleSocketServer server=null;
    	try {
    		server=new SimpleSocketServer();
    		System.out.println( "SimpleSocketServer03.Start server on port: " + server.port );
			server.startServer();
			/*
        	// Automatically shutdown in 1 minute
        	try {
            	Thread.sleep( 60000 );
        	} catch( Exception e ) { e.printStackTrace(); }
			 */
		} catch (Exception e) {	
			if (server!=null) {
				server.stopServer();
				System.out.println("server stopped");
				e.printStackTrace();
			}	
		}	
    }
}


ServerRequestHandler.class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package u.requests.sockets;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Properties;

import utils.CmdUtils;

class ServerRequestHandler extends Thread {

    private Socket socket;
    private String command="";
    //private String myPath="";
    private String shellsFolder="";
    private String jarsFolder="";
    private String javaPath="";
    private Properties props=null;
    private Boolean isError=false;
    private BufferedReader inSocket =null;
    private PrintWriter outSocket=null;
    private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    
    public ServerRequestHandler(Socket socket, Properties props ) { 
    	this.socket = socket; 
    	this.props=props;
    }
    
    @Override
    public void run() {
    	
        try {
            System.out.println( "RH-->Received a connection" );

            // Get input and output streams
            inSocket = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
            outSocket = new PrintWriter( socket.getOutputStream(), true );
            
            
            command = inSocket.readLine();
            outSocket.println("4.1 Des del server: Obtenint comandament a executar...("+ command +")");
            
            if (command.trim().length() <2) showError("ERROR: Comandament curt:("+ command + ")");
            else {
            	try {
            	
				shellsFolder=props.getProperty("shells.folder").trim();
				jarsFolder=props.getProperty("jars.folder").trim();
				javaPath=props.getProperty("java.path").trim();
				//command=myPath.trim() + File.separator+ command;
				command=command
						.replace("JAVA_PATH", javaPath)
						.replace("SHELLS_FOLDER", shellsFolder)
						.replace("JARS_FOLDER", jarsFolder);
				System.out.println("COMMAND="+ command);
				} catch (Exception e) { showError("ERROR : Obrint paràmetres del fitxer de propietats... app.properties" + e.getMessage());}
		    }
            if (!isError) {
            	outSocket.println("4.2 Des del server: Executant...("+ command +")");
            	try {
    				//Execute and wait for end of execution
    				if(CmdUtils.execProgram(command, null, null, false, outputStream) !=0) showError("ERROR 4: Executant " + command + "... ");
    			}catch (Exception e) { showError("ERROR 4.2: Executant " + command + "... " + e.getMessage());}
    		}
            
            if (!isError) {
            	outSocket.println("4.3 Des del server: Informant ...");
            	try {
    				//Show execution details
            		int i=0;
            		for (String line : outputStream.toString().split("\\n")) {
            			String lineUP=line.toUpperCase();
            			if (lineUP.contains("SHOWMESS") || lineUP.contains("ERROR")) {
            				outSocket.println("4.3." + ++i + "-->" + line);
            				System.out.println("4.3." + i + "-->" + line);
            			}	
            		}	
    			}catch (Exception e) { showError("ERROR 4.3: Executant " + command + "... " + e.getMessage());}
    		}
            
            if (!isError) outSocket.println("FIN:");  //END OF PROCESS SIGNAL!!
                      
            // Close our connection
            inSocket.close();
            outSocket.close();
            socket.close();

            System.out.println( "Rh:->Connection closed" );
        } catch( Exception e ) { e.printStackTrace(); }
    }
        
    private void showError(String message) {
    	isError=true;
        outSocket.println(message);
    }
}



36. My Wonderful Web Framework. (1) Actions. YAML definition

 1.  Actions

Falta definir els métodes de la classe actual. Cal fer un wrapper per a que una classe  que assumisca una cride d'una altra i que herede de BaseExecute!!!!!


An action consists of executing a method from a class. The actions can be defined in menus and forms:

  1. Actions that are executed in a "MenuItem" of type "action" ("command" and "1" are synonymous). Only one action per "MenuItem" is allowed
  2. Actions that are executed inside a form (represented by a class) in a "MenuItem" of type "form" (take into account that there are several types of forms: default(2), custom(3) and yaml (4)
An action is defined by:
  1. The class that contains the method to execute
  2. The method to execute
  3. The parameters that accept the class. That can be fixed and/or asked by means of a form
  4. The type of action: the possible values are: inner: In this case the class to execute is in the same Vaadin module, shell: In this case a call to a shell script in the property "execution.folder" of the property file "app.properties", socket: This is very important as delegates the execution to another server by means of sending messages through sockets, avoiding java RMI
  5.  Optional information: role group that is allowed to execute the program, icon to show, ask for confirmation, char to show in combo in grid form, if need a record id to operate,

2. Defining the actions

Here is a snippet of YAML code defining both types of actions $$$


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
menuItems: 
  [
    #1. MenuItem with an action of type socket (executed in another server (action: socket type))
    { description: firstcontrolloadyaml_control, className: openadmin.actions.SocketActions, icon: fa-upload,   
     type: action, defaultActions: false,
     actions: 
       [ 
         { name: dataLoad,  type: socket, method: execute,    
           fixParams: '[STRING:JAVA_PATH -jar JARS_FOLDER/IF/MiJar.jar FIRSTCONTROLLOAD]', 
           roleGroup: gadmin, icon: fa-upload, executedInGrid: false, needId: false  
         } 
       ]
    },  

    #2. MenuItem with an action of type inner (executed in a class included in the "war" file (action: inner type))
    { className: openadmin.actions.u.request.MetadataLoader, icon: fa-upload,   
      type: action, defaultActions: false, 
      actions: 
        [ 
          { name: dataLoad,  method: dataLoad,  fixParams: '[DAO-METADATA]',   
            roleGroup: gadmin,   icon: fa-upload, executedInGrid: false, needId: false  
          } 
        ]
    },
   
    #3. A form of type custom that shows the "users" and has som actions 
    { className: openadmin.model.control.User, description: User_Custom, icon: fa-user, combo: false, type: custom,
      actions: 
        [ 
          #3.1 Action in the form that calls a java program in another server (action: socket type)
          { name: getFactures, confirm: false, idChar: E, type: socket, method: execute, 
            fixParams: '[STRING:JAVA_PATH -jar JARS_FOLDER/IF/MiJar.jar AYTOSOPERATION]', 
            formParams: '{name: s_n, type: STRING, length: 1, value: S, required: true }', 
            roleGroup: gnew,   icon: fa-file-invoice-dollar, executedInGrid: false, needId: false  
          } ,
          
          #3.2 Example where the class of the action is a shell script executed in the same server (action: shell type)
          { name: dni,  confirm: false, idChar: 7, method: verifyDNI, roleGroup: gadmin,  type: shell, icon: fa-id-card, 
            fixParams: '[STRING:bshComprobaciones.sh];[ID];[STRING:verifyDNI]'    
          } ,

          #3.3 Example where the class of the action is the same class (action: inner type)
          { name: kkerr, confirm: false,   idChar: 8, type: inner,  method: kk1,            
            fixParams: '[ID]', roleGroup: gadmin,   icon: fa-cloud-showers-heavy    
          },  
          
          #3.4 Action in the form that calls a shell script in another server (action: socket type)
          { name: prova, confirm: true,    idChar: C, type: socket, executedInGrid: false,  
           fixParams: '[STRING:SHELL_PATH/shProva01.sh]', roleGroup: gadmin,   icon: fa-beer    
          } 
        
        ]
    }
  ] 


Line 4 defines a "menuitem" that is of type action, so it has only one action of type socket

Line 14 defines a "menuitem" that is of type action, so it has only one action of type inner


Line 26 defines a form with 4 actions of different types Socket (external java program execution), shell (in the same server), inner with a method of the "entity class" and another Socket (external shell script in another server)

Here are some important parameters:
  • className: The class that owns the method to execute, by default is the class shown in the form
  • method: the name of method of the class to execute. By default is the "execute"
  • name: The name that will be shown to the user and will be converted to i18n (internationalization)
  • roleGroup: the authorized group top execute the method
  • idChar: The char to show in the group of actions (in the combo) to be executed in a form. It must be set when the actions are executed from a form. Each action should have a unique "idChar"
  • icon: The name of the "font-awesome icon" to show.
  • needId: If the action is executed on one record (that is identified by its Id). The Id is supplied by the current record.
  • executedInGrid: If this action can be executed in a record of the grid and showed in the combo of options
  • confirm: If ask for confirmation of the user before executing the action. 

3. The fixParams parameter

This parameter is important as it has a peculiar format. It is a list of constant parameters. Every parameter must be between "square brackets" "[ ]" and separated by a colon ";"

Here is an example:


fixParams: '[DAO-H2];[ID];[INT:2];[LONG:9];[STRING:cat];[SERVER:0]', 

Where:
  • DAO-H2 is the DAO Object form the H2 database, there ara also other DAOs to other persistence units.
  • ID is the id of the Base Object
  • INT:2 is the integer value "2"
  • LONG:9 is the long value "9"
  • STRING:cat is the String value "cat"
  • SERVER:0 returns the server + ":" + port for specifiying a socket to a server to pass messages.
Al all these parameters are stored in a List

4. The formParams parameter

This parameter is important as it has a peculiar format. It is a list of parameters that will be required within a form to the user.

Each parameter is defined between brackets "{ }" and forms a YAML structure.


formParams: '{name: year, type: INT, length: 4, value: $YEAR, required: true }, {name: registro, type: INT, length: 6, value: 0, required: true }',

The labels of the parameters are:
  • name: name of the parameter
  • type: INT, LONG, STRING ...
  • length: max number of chars of the parameter 
  • required; true of false
  • value: default value. It can be:
    • $YEAR : Actual year
    • $YEAR_PREVIUOS_MONTH: Year of the previous month
    • $MONTH: Actual month (1 to 12) 
    • $PREVIOUS_MONTH: The previous month (1 to 12)
    • A constant