martes, 17 de marzo de 2020

JavaFX (3) Empaquetar/distribuir la aplicación (2/2)

1. Introducción

Ahora ya sabemos como crear el pom.xml para empaquetar nuestra aplicación.

2. Clases importantes en JavaFx y la clase a ejecutar

Este punto es muy importante, despúes del pom.xml

JavaFx tiene una clase principal, en este caso "App" que hereda de Application. Esta clase debe de tener los métodos "start" y "main". Con esta clase solo (junto con las clases que hace referencia) se puede ejecutar la aplicacion tral como se indica en el punto siguiente (con maven clean javafx:run)

Pero si queremos tener un jar para ejecutar esta aplicación hay que crear una clase (en mi caso NewMain) que contenga un main que llame al main de la clase que hereda de Application (en mi caso App)


package ximodante.JavaFX01;

public class NewMain {
    public static void main(String[] args) {
        ximodante.JavaFX01.App.main(args);
    }
}

Esta clase la "NewMain" es la que hay que indicar al "pom.xml" (línea 151) que hace referencia el maven-jar-plugin en su clausula <mainClass>

Mientras que la clase App es:


  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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package ximodante.JavaFX01;

import javafx.application.Application;
//import javafx.fxml.FXMLLoader;

import javafx.scene.Scene;
import javafx.scene.control.Button;

import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
//import javafx.stage.Modality;
import utils.CompressUtils;
import utils.FileUtils;
import utils.PropertiesUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;

/**
 * JavaFX App
 */
public class App extends Application {
   
 //boolean isJar=false;
 boolean isJar=true;
 Properties props=null;    
 String propsFileName="app.properties";
 String propsFileAbsoluteName="";
 String DBFile="";
 String destinationFolder="";
 
    @Override
    public void start(Stage stage) throws IOException {
     //stage.initModality(Modality.APPLICATION_MODAL);  //NO VA
     stage.initStyle(javafx.stage.StageStyle.DECORATED);
     //stage.setFullScreen(true);
     stage.setTitle("Copia de seguretat de la BD");
     
     try {
      if (isJar) {
       props=PropertiesUtils.getRelativeFromJarFileProperties(propsFileName);
       propsFileAbsoluteName=FileUtils.getJarContainingFolder() + File.separator + propsFileName;
      }
      else {
       props=PropertiesUtils.getRelativeProperties(propsFileName);
       URL url = ClassLoader.getSystemResource(propsFileName);
       propsFileAbsoluteName=url.getPath();
      }
  } catch (Exception e2) {
   // TODO Auto-generated catch block
   e2.printStackTrace();
   props=null;
  }
     
     File propsFile= new File(propsFileAbsoluteName);
     if (props!=null && propsFile.exists()) {
      DBFile=props.getProperty("db.name");
         destinationFolder=props.getProperty("destination.folder");
     } else props=null; 
     
     System.out.println("DBFile=" + DBFile);
     
     //TextField txtDBFileName = new TextField();
     TextArea txtDBFileName = new TextArea();
     txtDBFileName.setPrefWidth(600);
     txtDBFileName.setPrefHeight(60);
     txtDBFileName.setText(DBFile);
     //txtDBFileName.setText("");
     
     TextArea txtFolderName = new TextArea();
     txtFolderName.setPrefWidth(600);
     txtFolderName.setPrefHeight(60);
     txtFolderName.setText(destinationFolder);
     //txtFolderName.setText("");
     
     
     TextField txtMessage = new TextField();
     txtMessage.setStyle("-fx-font-weight: bold;");
     
     FileChooser fileChooser = new FileChooser();
     fileChooser.getExtensionFilters().addAll(
           new FileChooser.ExtensionFilter("Acces DB <2003 mdb", "*.mdb")
          ,new FileChooser.ExtensionFilter("Acces DB >2003 accdb", "*.accdb")
      );
     
     DirectoryChooser directoryChooser = new DirectoryChooser();
     
     
     Button btSelectFile = new Button("Tria el fitxer de la BD");
     btSelectFile.setPrefHeight(40);
     
     
     Button btSelectFolder = new Button("Tria la carpeta on fer la còpia comprimida");
     btSelectFolder.setPrefHeight(40);
     
     Button btOK= new Button("Fes la còpia");
     btOK.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
     btOK.setPrefHeight(60);
     
     
     
     
     //Actions
     btSelectFile.setOnAction(e -> {
      try {
       DBFile=txtDBFileName.getText().trim();
       File DBFolder=null;
          if (DBFile!=null && DBFile.length()>0) DBFolder=new File(new File(DBFile).getParent());
          if (DBFolder !=null && DBFolder.exists()) fileChooser.setInitialDirectory(DBFolder);
         } catch (Exception e2) {
       // TODO Auto-generated catch block
       e2.printStackTrace();
      } 
      File selectedFile = fileChooser.showOpenDialog(stage);
            if (selectedFile!=null && selectedFile.exists()) txtDBFileName.setText(selectedFile.getAbsolutePath());
        });
     
     
     btSelectFolder.setOnAction(e -> {
      try {
       destinationFolder=txtFolderName.getText().trim();
          File destFolder=new File(destinationFolder);
          if (destFolder!=null && destFolder.exists()) directoryChooser.setInitialDirectory(destFolder);
         } catch (Exception e2) {
       // TODO Auto-generated catch block
       e2.printStackTrace();
      }
      File selectedDirectory = directoryChooser.showDialog(stage);
            if (selectedDirectory!=null && selectedDirectory.exists()) txtFolderName.setText(selectedDirectory.getAbsolutePath());
        });
     
     btOK.setOnAction(e -> {
      if (txtDBFileName.getText().trim().length()>1 && 
        txtFolderName.getText().trim().length()>1 )
    try {
     DBFile=txtDBFileName.getText().trim();
        destinationFolder=txtFolderName.getText().trim();
        File f1=null;
        File f2=null;
        try {
         f1=new File(DBFile);
         f2=new File(destinationFolder);
        }catch (Exception e3) {
         e3.printStackTrace();
        }
        System.out.println (f1.toString());
        System.out.println (f2.toString());
     if (f1!=null && f2!=null && f1.exists() && f2.exists()) {
      String fitxerEixida=CompressUtils.compresFiletoFolder(DBFile, destinationFolder);
     
      txtMessage.setText("Copia de seguretat realitzada correctament!-->" + fitxerEixida);
      if (props!=null) {
       props.setProperty("db.name", DBFile);
       props.setProperty("destination.folder", destinationFolder);
       PropertiesUtils.saveRelativeFromJarFileProperties(props, propsFileName, "");
      }
     } else {
      String msg="";
      if (f1==null || !f1.exists()) msg=msg + " No existeix el fitxer " + DBFile;
      if (f2==null || !f2.exists()) msg=msg + " No existeix la carpeta " + destinationFolder;
      txtMessage.setText(msg);
     }
    } catch ( Exception e1) {
     e1.printStackTrace();
    }
        });
     
     
     GridPane gridPane = new GridPane();
     gridPane.setHgap(20);
     gridPane.setVgap(20);
     
     gridPane.add(btSelectFile  , 1 , 0, 1 , 1);
     gridPane.add(txtDBFileName , 2 , 0, 20, 1);
     gridPane.add(btSelectFolder, 1 , 1, 1 , 1);
     gridPane.add(txtFolderName , 2 , 1, 20, 1);
         
     gridPane.add(btOK          , 19, 2, 1 , 1);
     
     gridPane.add(txtMessage    , 1 , 3, 21, 1);
     
        Scene scene = new Scene(gridPane, 950, 350);
        stage.setScene(scene);
        stage.show();
    }
 
    public static void main(String[] args) {
        launch(args);
    }
    
    /*
    
    @Override
    public void start(Stage stage) throws IOException {
        scene = new Scene(loadFXML("primary"), 640, 480);
        stage.setScene(scene);
        stage.show();
    }
 
    static void setRoot(String fxml) throws IOException {
        scene.setRoot(loadFXML(fxml));
    }
    
    private static Parent loadFXML(String fxml) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
        return fxmlLoader.load();
    }
    */
}

Hay un fichro de propiedades "app.properties" (en ser/main/properties) que contiene solo 2 valores


db.name = /home/eduard/sindrec.mdb
destination.folder = /home/eduard/Documents

3. Classes de Utilidad

Tenemos 3 clases de utilidades con métodos estáticos que son:
1. CompressUtils (para comprimir ficheros)
2. FileUtils (Para trabajar con ficheros)
3. PropertyUtils (para manejar propiedades)

Veamos CompressUtils.java


package utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/*
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.io.FilenameUtils;
*/


public class CompressUtils {

 /*
 public static void compresFiletoFolder(String fileToArchiveName, String destinationFolderName) throws IOException {
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  String date = sdf.format(new Date()); 
  String fName=FilenameUtils.getName(fileToArchiveName);
  String compressedFileName=destinationFolderName+ "/"+ fName +"."+ date + ".7z";
  System.out.println(compressedFileName) ; 
  SevenZOutputFile sevenZOutput = new SevenZOutputFile(new File(compressedFileName));
  
  SevenZArchiveEntry entry = sevenZOutput.createArchiveEntry(new File(fileToArchiveName), fName);
  sevenZOutput.putArchiveEntry(entry);
  sevenZOutput.write(Files.readAllBytes(Paths.get(fileToArchiveName)));
  sevenZOutput.closeArchiveEntry();
  sevenZOutput.close(); 
 }
 */
 
 public static String compresFiletoFolder(String fileToArchiveName, String destinationFolderName) throws IOException {
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  String date = sdf.format(new Date()); 
  
  File fileToZip = new File(fileToArchiveName);
  String fName=fileToZip.getName(); 
  
  String compressedFileName=destinationFolderName+ "/"+ fName +"."+ date + ".zip";
  System.out.println(compressedFileName) ; 
  
  FileOutputStream fos = new FileOutputStream(compressedFileName);
        ZipOutputStream zipOut = new ZipOutputStream(fos);
  
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
        zipOut.putNextEntry(zipEntry);
        
        byte[] bytes = new byte[1024];
        int length;
        while((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        zipOut.close();
        fis.close();
        fos.close();
        return compressedFileName;
 }
 
 public static void main(String[] args) {
  try {
   compresFiletoFolder("/home/eduard/Documents/Edu_basquet.pdf", "/home/eduard");
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }

}

Veamos FileUtils.java



package utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URLDecoder;
import java.security.CodeSource;

public class FileUtils {

 /**
  * Folder that contains jar file or folder that contains project folder
  * @return
  * @throws Exception
  */
 public static String getJarContainingFolder() throws Exception {
  Class<?> aClass=MethodHandles.lookup().lookupClass();
  CodeSource codeSource = aClass.getProtectionDomain().getCodeSource();

  File jarFile;

  if (codeSource.getLocation() != null) {
   jarFile = new File(codeSource.getLocation().toURI());
  } else { // It is not a jar file
   String path = aClass.getResource(aClass.getSimpleName() + ".class").getPath();
      String jarFilePath = path.substring(path.indexOf(":") + 1, path.indexOf("!"));
      jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
      jarFile = new File(jarFilePath);
  }
  String s=jarFile.getParentFile().getAbsolutePath();
  System.out.println("S------>:" + s);
  if (s.endsWith(File.separator+"target")) { // Maven target directory for compiled classes
   s=s.substring(0, s.lastIndexOf(File.separator));
   s=s.substring(0, s.lastIndexOf(File.separator));
  } 
  return s;
 }
 
 public static byte[] readFile(String fileName) throws IOException {
  File file = new File(fileName);//filename should be with complete path
  FileInputStream fis = new FileInputStream(file);
  byte[] b = new byte[ (int) file.length()];
  fis.read(b);
  fis.close();;
  return b;
 }
 
 public static void writeToFile(String fileName, String myString) throws IOException {
  BufferedWriter writer = new BufferedWriter( new FileWriter( fileName));
  writer.write( myString);
  // do stuff 
  writer.close();
 }
 
 /**
  * test in main class
  * @param args
  */
 public static void main(String[] args) {
  try {
   System.out.println(getJarContainingFolder());
   System.out.println(System.getProperty("java.class.path"));
   System.out.println(System.getProperty("user.dir"));
   byte[] b=readFile("/home/eduard/Audit.0.log");
   String sb=new String(b);
   System.out.println(sb);
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }

}

Y por último PropertiesUtils.java



package utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;

import java.util.Properties;

public class PropertiesUtils {
 
 // Valid only in web
 //public static String ResourcesPath=Thread.currentThread().getContextClassLoader().getResource("").getPath();
 
 
 /**
  * The properties path is referred relatively to resources path
  * @param filePath
  * @return
  * @throws FileNotFoundException
  * @throws IOException
  */
 public static Properties getRelativeProperties(String filePath) throws FileNotFoundException, IOException {
  Properties properties = new Properties();
       
  //String path = ResourcesPath + filePath;
  //System.out.println("PROPERTIES.PATH=" + path);
  //properties.load(new FileInputStream(path));
  
  
  System.out.println(filePath);
  //URL url = ClassLoader.getSystemResource("/resources/" + filePath);
  //URL url = ClassLoader.getSystemResource("/src/main/resources/" + filePath);+
  URL url = ClassLoader.getSystemResource(filePath);
  System.out.println(url);
  properties.load(url.openStream());
  
  
  for(Object o: properties.keySet()) System.out.println(o.toString() + "-->"+ properties.getProperty(o.toString()));
  
  return properties;
 }
 
 /**
  * Leemos la propiedades de una ruta absoluta.
  * OJO: Si queremos leer un fichero de propiedades "file.properties" que se encuentra 
  *      en la misma carpeta que el proyecto (o el jar ejecutable) :
  *   
  *      final File f = new File(PropertiesUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath());
  *      
  *      String suffix=File.separator +"..";
     *      //myFile=f.getAbsolutePath()+ "/../../../file.properties" ;
     *      myFile=f.getAbsolutePath()+ StringUtils.repeat(suffix, 3) + File.separator + file.properties" ;
     *      Properties myProps=PropertiesUtils.getAbsoluteProperties(myFile);
     *      
  * @param filePath
  * @return
  * @throws FileNotFoundException
  * @throws IOException
  */
 public static Properties getAbsoluteProperties(String filePath) throws FileNotFoundException, IOException {
  Properties properties = new Properties();
       
  //String path = ResourcesPath + filePath;
  System.out.println(filePath);
  properties.load(new FileInputStream(filePath));
  
  
  
  
  for(Object o: properties.keySet()) System.out.println(o.toString() + "-->"+ properties.getProperty(o.toString()));
  
  return properties;
 }
 
 /**
  * Gets the folder where resides the executable jar
  * @return
  */
 /**
 public static String getJarFolderPath() {
  
  final File f = new File(PropertiesUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath());
     String suffix=File.separator +"..";
     //jarFolder=f.getAbsolutePath()+ "/../../../"file.properties" ;
     String jarFolder=f.getAbsolutePath()+ StringUtils.repeat(suffix, 3) + File.separator;
     return jarFolder;
 }
 */
 public static Properties getRelativeFromJarFileProperties(String filePath) throws Exception {
  Properties properties = new Properties();
  
  //String myPath=getJarFolderPath()+filePath;
  String myPath=FileUtils.getJarContainingFolder() + File.separator + filePath;
  System.out.println(myPath);
  //System.out.println(PropertiesUtils.class.getResource(PropertiesUtils.class.getSimpleName() + ".class").getPath());
  //System.out.println(System.getProperty("user.dir"));
  properties.load(new FileInputStream(myPath));
  
  for(Object o: properties.keySet()) System.out.println(o.toString() + "-->"+ properties.getProperty(o.toString()));
  
  return properties;
 }
 
 
 public static void saveAbsoluteProperties(Properties properties, String filePath, String comments) throws FileNotFoundException, IOException {
  //String path = ResourcesPath + filePath;
  properties.store(new FileOutputStream(filePath), comments);
 }
 
 public static void saveRelativeFromJarFileProperties(Properties properties, String filePath, String comments) throws Exception {
  String myPath=FileUtils.getJarContainingFolder() + File.separator + filePath;
  System.out.println(myPath);
  properties.store(new FileOutputStream(myPath), comments);
 }
 
 public static void main(String[] args) throws FileNotFoundException, IOException{
  //String myFile="properties/last_fac.properties";
  String myFile="properties/application.properties";
  Properties myProps=getRelativeProperties(myFile);
  myProps.setProperty("A", "9999.888");
  myProps.setProperty("J", "1111.222");
  //for(Object o: myProps.keySet()) System.out.println(o.toString() + "-->"+ myProps.getProperty(o.toString()));
  //String myFile2="/home/eduard/last_fac2.properties";
  //saveAbsoluteProperties (myProps,myFile2, "ultima actualizacion");
  //myFile="../kk.properties";
        
         /*
               ws/pr/tg/cl/file  
         myFile="../../../../kk.properties";
         Properties myProps1=getRelativeProperties(myFile);
         System.out.println(myProps1.getProperty("prop2"));
         */
  /*
      System.out.println("-------------------------");
         //myFile="kk.properties";
      myFile="";
         String prefix="../";
         for (int i=0; i<15; i++) {
          System.out.println(myFile);
          URL url = ClassLoader.getSystemResource(myFile);
          System.out.println(url);
          System.out.println("-------------------------");
          myFile=prefix+myFile;
         }
        */
  /*
        final File f = new File(PropertiesUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath()); 
        System.out.println(f.getAbsolutePath());
        myFile=f.getAbsolutePath();
        String sufix="/..";
        for (int i=0; i<15; i++) {
      System.out.println(myFile);
      URL url = new URL("file:"+myFile);
      System.out.println(url);
      url=new URL("file:"+myFile+"/kk.properties");
      System.out.println(url);
      System.out.println("-------------------------");
      myFile=myFile+sufix;
     }
     */
  System.out.println("----------------------------------------------");
  final File f = new File(PropertiesUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath()); 
        System.out.println(f.getAbsolutePath());
        String suffix=File.separator + "..";
        myFile=f.getAbsolutePath();
        for (int i=0; i<10; i++) {
      System.out.println(myFile);
      String propFile=myFile + File.separator + "kk.properties";
      System.out.println(propFile);
      try {
       Properties myPrp=getAbsoluteProperties(propFile);
       System.out.println(myPrp.getProperty("prop1"));
      } catch (Exception e) {
       e.printStackTrace();
      }
      System.out.println("-------------------------");
      myFile=myFile+suffix;
     }
  
 }
}

4. Ejecutar la aplicación 

Ya no vale con seleccionar la clase ejecutable y darle "Run-As -> Java Application"  (no funciona)

Para ejecutar la aplicación con Eclipse y Maven hay que seguir estos pasos:

4.1 Boton derecho sobre el nombre del proyecto
4.2 Run as
4.3 Maven build...
4.4 Goals -> "clean javafx:run"

Y en mi caso tenemos esta ejecución



5. Empaquetar y distribuir


5.1 Boton derecho sobre el nombre del proyecto
5.2 Run as
5.3 Maven build...
5.4 Goals -> "clean install"

Y me genera la carpeta "MiCarpeta" dentro de "target" que contiene el jar (MiJar.jar) y la carpeta "lib" con todas las librerías en forma de ficheros ".jar"

6. Ejecutar el jar

Tener en la variable de entorno path la ruta a la carpeta "bin" del openjdk13 e ir a la carpeta "MiCarpeta" y ejecutar "java -jar MiJar.jar" y a funcionar (salvo que el path tenga la ruta a java de otra versión anterior, en cuyo caso habrá que copiar el ejecutable java (java o java,exe) a java_ximo (o java_ximo.exe) i ejecutar "java_ximo -jar MiJar.jar". Hay que tener en cuenta que no hace falta instalarlo ni en windows ni en linux, se puede copiar el OpenJdk13 a una carpeta y enrutarlo con el path.

7. Posteriores empaquetados y ejecuciones en Eclipse

Si ya se han ejecutado estas opciones , selecionar "Maven build" en vez de "Maven build.."
Si se quiere borrar o modificar algunas de las configuraciones del build se hace:

7.1 Boton derecho sobre el nombre del proyecto
7.2 Run as
7.3 Run Configurtations
7.4 Selecionar la configuracion deseada y modificarla o borrarla.