Un ejemplo completo y funcional, con el código fuente tanto del servidor como del cliente, de una mini tienda online. La aplicación, desarrollada en Java, usando RMI, inicia un servicio RMI en el servidor, los clientes se conectarán a este servidor para iniciar sesión (si no existe el usuario la aplicación lo creará) y poder gestionar usuarios y productos de forma remota (listar, crear, eliminar, etc.). Se utilizará la serialización de Java y se almacenarán los objetos Java en persistencia con JSON.
- Ejemplo Java para realizar una tienda online con productos y usuarios en cliente/servidor.
- Requisitos para desarrollar la aplicación Java RMI Tienda Online con persistencia en JSON.
- Crear clases necesarias para la aplicación Servidor en Java RMI.
- Crear clases necesarias para la aplicación Cliente en Java RMI.
- Compilar el cliente y el servidor desde IntelliJ IDEA para generar los .class y ejecutar en sistema operativo.
- Descarga del código fuente completo en Java con el IDE IntelliJ IDEA.
- La aplicación Tienda Online Java RMI en funcionamiento.
- Algunos errores y su posible solución.
Ejemplo Java para realizar una tienda online con productos y usuarios en cliente/servidor
Se pide crear un servidor Java que permita ejecutar los siguientes métodos remotos:
- inicioSesionUsuario: se le pasará como parámetro el nombre de usuario y la contraseña y devolverá un boolean indicando si el usuario y contraseña es correcta.
- altaUsuario: insertará un nuevo usuario en el sistema. Se le pasará un objeto Usuario como parámetro.
- listarUsuarios: devolverá una lista de objetos Usuario con todos los usuarios del sistema.
- altaProducto: insertará un nuevo producto en el sistema. Se le pasará un objeto Producto como parámetro.
- listarProductos: devolverá una lista de objetos Producto con todos los productos del sistema.
- eliminarProducto: elimina un producto del sistema. Se le pasará el código del producto a eliminar como parámetro. Devolverá un boolean, indicando se se ha eliminado el producto
- nombreServidor: método remoto que únicamente devuelve un string desde el servidor. Se utilizará para hacer un test de la conexión RMI.
Se pretende simular una «mini» tienda online, en la que podremos iniciar sesión con un usuario y contraseña y listar los productos de la tienda, añadir nuevos productos y eliminar productos. También podremos listar los usuarios de acceso a la aplicación y añadir nuevos usuarios.
La persistencia se realizará en JSON. Se almacenarán los productos en el fichero productos.json y los usuarios en el fichero usuarios.json.
Para almacenar la contraseña del usuario se usará el hash MD5, en lugar de almacenar la contraseña en texto plano. De esta forma, aunque se tenga acceso al fichero usuarios.json, las contraseñas están codificadas y no pueden ser leídas.
Para realizar la tienda virtual se usará programación distribuida. En el caso de Java, usaremos RMI, teniendo un equipo que hará de servidor y el resto de clientes que se conectarán a este servidor. Por lo tanto desarrollaremos dos aplicaciones, una Servidor y otra Cliente, separadas. En la aplicación servidor definiremos los métodos y clases para iniciar el servidor RMI y los métodos para la persistencia y manipulación de datos. Dichos métodos serán remotos, de forma que podrán ejecutarse desde los clientes RMI. Por parte de la aplicación Cliente, se implementarán los métodos de conexión con el servidor RMI y de ejecución de los métodos remotos del servidor para manipulación de datos y gestión de la tienda virtual.
Requisitos para desarrollar la aplicación Java RMI Tienda Online con persistencia en JSON
En el siguiente artículo explicamos lo que es RMI de Java y un ejemplo sencillo de su implementación:
Los únicos requisitos para desarrollar en Java son:
- Disponer de un editor de textos plano, o bien de un IDE de desarrollo avanzado. En nuestro caso usaremos IntelliJ IDEA.
- Para la compilación de la aplicación necesitaremos disponer de Java instalado, bien sea un equipo con Windows, Linux o macOS.
Todo ello, incluso la apertura de puertos y el mapeo si queremos acceder desde fuera de nuestra organización, desde Internet, por RMI, al servidor, está explicado en el artículo anterior.
Crear clases necesarias para la aplicación Servidor en Java RMI
Para la conexión cliente/servidor usaremos RMI, como hemos comentado. Definiremos una aplicación servidor que será la encargada de iniciar el registro RMI en el puerto indicado. En la aplicación servidor tendremos una interfaz remota (Remote), llamada TiendaOnlineRMI, para definir los métodos remotos (los que podrá invocar el cliente), con los métodos indicados anteriormente: inicioSesionUsuario, altaUsuario, listarUsuarios, altaProducto, listarProductos, eliminarProducto y nombreServidor.
La implementación de esta interfaz remota la realizaremos en la clase TiendaOnlineRMIImpl, que contendrá los métodos remotos y su código asociado. Será la que acceda al repositorio JSON para invocar a los métodos de acceso a datos (alta de usuarios, listado de usuarios, alta de productos, listado de productos y baja de productos).
En la interfaz RepositorioBD del servidor se definirán todos los métodos de acceso a datos, al repositorio:
- altaUsuario: añadir un nuevo usuario.
- obtenerUsuario: obtener un usuario por su nombre de usuario.
- obtenerUsuarios: obtener todos los usuarios de la tienda.
- altaProducto: añadir un nuevo producto a la tienda.
- obtenerProductos: obtener todos los productos de la tienda.
- eliminarProducto: eliminar un producto de la lista por su código.
El código fuente de los métodos anteriores se definirá en la clase JsonBDRepositorio que será una implementación de la interfaz RepositorioBD. Para la persistencia de los datos en JSON usaremos el paquete Java de Goolge Gson, que permite serializar y deserializar objetos Java en JSON. Por lo tanto necesitaremos añadir el import correspondiente en la clase JsonBDRepositorio:
import com.google.gson.Gson;
Y disponer de dicha biblioteca Gson, que incluimos en la descarga del ejemplo completo de este artículo.
El servidor contará con una clase principal Main, que será la encargada de solicitar la IP o nombre DNS y el puerto donde se ejecutará y registrará el objeto Registry RMI, para permanecer a la escucha de la conexión de los clientes.
Por último, para el servidor, contendrá dos clases con los atributos, setters y getters de los objetos Usuario (Usuario.java) y Producto (Producto.java), que serán los que serialicemos y almacenemos en JSON. Para ello se creará la clase Producto (serializable), con los atributos:
- codigo: identificador del producto.
- nombre: nombre del producto.
- precio: precio del producto.
Se establecerán los setters y getters correspondientes para los atributos anteriores.
Para la clase Usuario, tendremos los atributos:
- nombe: nombre del usuario con el que se iniciará sesión (nick).
- contraseña: contraseña del usuario, se almacenará el hash MD5.
- email: cuenta de correo electrónico del usuario.
- teléfono: teléfono del usuario.
A continuación mostramos el código fuente completo de cada clase descrita anteriormente para la parte del Servidor RMI. Las partes importantes del código van comentadas con su utilidad y explicación.
- Producto.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 |
package com.main.models; import java.io.Serializable; public class Producto implements Serializable { //Identificador del producto private String codigo; //Nombre del producto private String nombre; //Precio del producto private float precio; //Constructor public Producto() { } // Obtener el código del producto public String getCodigo() { return codigo; } // Establecer el código del producto public void setCodigo(String codigo) { this.codigo = codigo; } //Obtener el nombre del producto public String getNombre() { return nombre; } // Establecer el nombre del producto public void setNombre(String nombre) { this.nombre = nombre; } // Obtener el precio del producto public float getPrecio() { return precio; } // Establecer el precio del producto public void setPrecio(float precio) { this.precio = precio; } } |
- Usuario.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 |
package com.main.models; import java.io.Serializable; public class Usuario implements Serializable { //Nombre del usuario private String nombre; //Contraseña del usuario private String contrasena; //Email del usuario private String email; //Teléfono del usuario private String telefono; //Constructor public Usuario(){ } // Obtener el nombre de usuario public String getNombre() { return nombre; } // Establecer el nombre de usuario public void setNombre(String nombre) { this.nombre = nombre; } //Obtener la contraseña del usuario public String getContrasena() { return contrasena; } //Establecer la contraseña del usuario public void setContrasena(String contrasena) { this.contrasena = contrasena; } //Obtener el email del usuario public String getEmail() { return email; } //Establecer el email del usuario public void setEmail(String email) { this.email = email; } //Obtener el número de teléfono public String getTelefono() { return telefono; } //Estabelcer el número de teléfono public void setTelefono(String telefono) { this.telefono = telefono; } } |
- RepositorioBD.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 |
package com.main.repository; import com.main.models.Producto; import com.main.models.Usuario; import java.util.List; //Representa el repositorio de base de datos public interface RepositorioBD { //Añadir un nuevo usuario boolean altaUsuario(Usuario usuario); //Obtener un usuario por su nombre de usuario Usuario obtenerUsuario(String nombre); //Obtener todos los usuarios de la tienda List<Usuario> obtenerUsuarios(); //Añadir un nuevo producto a la tienda boolean altaProducto(Producto producto); //Obtener todos los productos de la tienda List<Producto> obtenerProductos(); //Eliminar un producto de la lista por su codigo boolean eliminarProducto(String codigo); } |
- JsonBDRepositorio.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 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 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
package com.main.repository; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.main.models.Producto; import com.main.models.Usuario; import java.io.*; import java.lang.reflect.Type; import java.util.List; //Implementación del repositorio para almacenamiento en JSON public class JsonBDRepositorio implements RepositorioBD { //Dirección de la carpeta en la que se encuentran los archivos de almacenamiento private static final String RUTA_BASE_BD = "/com/main/bd/"; //Nombre del fichero JSON de almacenamiento de usuarios private static final String FICHERO_BD_USUARIOS = "usuarios.json"; //Nombre del fichero JSON de almacenamiento de productos private static final String FICHERO_BD_PRODUCTOS = "productos.json"; //Listado de usuarios registrados private List<Usuario> usuarios; //Listado de productos private List<Producto> productos; //Conversor de archivos JSON a objetos Java final Gson gson; //Constructor public JsonBDRepositorio() { //Depurar System.out.println("Creando clase GSON de Google para persistencia en JSON..."); //Creando clase GSON de Google para persistencia en JSON this.gson = new Gson(); //Depurar System.out.println("Creada clase GSON de Google para persistencia en JSON -> OK"); this.usuarios = obtenerListaUsuarios(); this.productos = obtenerListaProductos(); } //Obtener el listado de usuarios del JSON como objetos Java private List<Usuario> obtenerListaUsuarios() { //Depurar System.out.println("Obteniendo listado de usuarios desde la persistencia JSON..."); System.out.println(FICHERO_BD_USUARIOS); String usuarioJSON = obtenerJSONString(FICHERO_BD_USUARIOS); final Type tipoListaUsuarios = new TypeToken<List<Usuario>>(){}.getType(); return gson.fromJson(usuarioJSON, tipoListaUsuarios); } //Obtener el listado de productos del JSON como objetos Java private List<Producto> obtenerListaProductos() { //Depurar System.out.println("Obteniendo listado de productos desde la persistencia JSON..."); System.out.println(FICHERO_BD_PRODUCTOS); String productoJSON = obtenerJSONString(FICHERO_BD_PRODUCTOS); final Type tipoListaProductos = new TypeToken<List<Producto>>(){}.getType(); return gson.fromJson(productoJSON, tipoListaProductos); } //Obtener el contenido de un fichero JSON del almacenamiento como String private String obtenerJSONString(String nombreFichero) { String dataString = null; try { File fichero = new File(getClass().getResource(RUTA_BASE_BD + nombreFichero).getFile()); BufferedReader reader = new BufferedReader(new FileReader(fichero)); StringBuilder strBuilder = new StringBuilder(); String linea; while ((linea = reader.readLine()) != null) { strBuilder.append(linea); } reader.close(); dataString = strBuilder.toString(); } catch (IOException e) { System.out.println("Error al obtener la lista de JSON: " + e.getMessage()); //e.printStackTrace(); } return dataString; } //Sincronizar el JSON de usuarios con los datos que tenemos en la lista private void sincronizarUsuariosAlmacenamiento() { String json = gson.toJson(this.usuarios); File file = new File(getClass().getResource(RUTA_BASE_BD + FICHERO_BD_USUARIOS).getFile()); guardarJSONFichero(json,file); } //Sincronizar el JSON de productos con los datos que tenemos en la lista private void sincronizarProductosAlmacenamiento() { String json = gson.toJson(this.productos); File file = new File(getClass().getResource(RUTA_BASE_BD + FICHERO_BD_PRODUCTOS).getFile()); guardarJSONFichero(json,file); } //Escribir un JSON en el fichero especificado private void guardarJSONFichero(String json, File fichero) { try { FileWriter writer = new FileWriter(fichero); writer.write(json); writer.close(); } catch (IOException e) { System.out.println("Error al guardar en JSON: " + e.getMessage()); //e.printStackTrace(); } } //Buscar un usuario por su nombre de usuario private Usuario buscarUsuarioPorNombre(String nombre) { //Para depurar System.out.println("Buscando usuario [" + nombre + "]..."); if (usuarios != null) { for (Usuario usuarioBusqueda : usuarios) { if (usuarioBusqueda.getNombre().equals(nombre)) { System.out.println("Encontrado usuario [" + nombre + "] -> OK"); return usuarioBusqueda; } } } //Si no se ha encontrado el usuario System.out.println("No se ha encontrado el usuario [" + nombre + "]"); return null; } //Buscar un producto por su código de producto private Producto buscarProductoPorCodigo(String codigo) { //Para depurar System.out.println("Buscando producto [" + codigo + "]..."); if (codigo != null) { if (productos != null) { //Para depurar System.out.println("Número de productos en BD [" + productos.size() + "]..."); for (Producto producto : productos) { if (producto != null && producto.getCodigo() != null) { if (producto.getCodigo().equals(codigo)) { //Para depurar System.out.println("Encontrado producto [" + codigo + "] -> OK"); return producto; } } } } } //Si no se ha encontrado el producto //o el código pasado como parámetro es null, devuelve null System.out.println("No se ha encontrado el producto [" + codigo + "]"); return null; } @Override public boolean altaUsuario(Usuario usuario) { //Comprobamos que no haya un usuario con el mismo nombre ya dado de alta if (buscarUsuarioPorNombre(usuario.getNombre()) != null) { //Para depurar System.out.println("El usuario [" + usuario.getNombre() + "] ya existe..."); return false; } //Para depurar System.out.println("Dando de alta usuario [" + usuario.getNombre() + "]..."); //Agregamos el usuario a la lista de objetos de usuarios Java usuarios.add(usuario); //Para depurar System.out.println("Dando de alta usuario [" + usuario.getNombre() + "] en JSON..."); //Guardamos la lista de objetos de usuario Java en el JSON (persistencia) sincronizarUsuariosAlmacenamiento(); //Para depurar System.out.println("Dado de alta usuario [" + usuario.getNombre() + "] en JSON -> OK"); return true; } @Override public Usuario obtenerUsuario(String nombre) { return buscarUsuarioPorNombre(nombre); } @Override public List<Usuario> obtenerUsuarios() { return usuarios; } @Override public boolean altaProducto(Producto producto) { if (buscarProductoPorCodigo(producto.getCodigo()) != null) { //Para depurar System.out.println("El producto [" + producto.getCodigo() + "] ya existe..."); return false; } //Para depurar System.out.println("Dando de alta producto [" + producto.getCodigo() + "]..."); //Agregamos el producto a la lista de objetos de productos Java productos.add(producto); //Para depurar System.out.println("Dando de alta producto [" + producto.getCodigo() + "] en JSON..."); //Guardamos la lista de objetos de producto Java en el JSON (persistencia) sincronizarProductosAlmacenamiento(); //Para depurar System.out.println("Dado de alta producto [" + producto.getCodigo() + "] en JSON -> OK"); return true; } @Override public List<Producto> obtenerProductos() { return productos; } @Override public boolean eliminarProducto(String codigo) { //Para depurar System.out.println("Eliminando producto [" + codigo + "]..."); Producto producto = buscarProductoPorCodigo(codigo); if (producto == null) return false; productos.remove(producto); //Para depurar System.out.println("Eliminado producto [" + codigo + "] -> OK"); return true; } } |
- TiendaOnlineRMI.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 |
package com.main.rmi; import com.main.models.Producto; import com.main.models.Usuario; import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List; public interface TiendaOnlineRMI extends Remote { //Inicio de sesión del usuario (login) boolean inicioSesionUsuario(String nombre, String contrasena) throws RemoteException; //Alta de nuevo usuario boolean altaUsuario(Usuario usuario) throws RemoteException; //Listar los productos de la tienda List<Usuario> listarUsuarios() throws RemoteException; //Alta de producto boolean altaProducto(Producto producto) throws RemoteException; //Listar los productos de la tienda List<Producto> listarProductos() throws RemoteException; //Eliminar un producto de la tienda boolean eliminarProducto(String codigo) throws RemoteException; //Para test de conexión String nombreServidor() throws RemoteException; } |
- TiendaOnlineRMIImpl.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 |
package com.main.rmi; import com.main.models.Producto; import com.main.models.Usuario; import com.main.repository.RepositorioBD; import com.main.repository.JsonBDRepositorio; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.List; public class TiendaOnlineRMIImpl extends UnicastRemoteObject implements TiendaOnlineRMI { RepositorioBD repositorio; //Constructor public TiendaOnlineRMIImpl() throws RemoteException { repositorio = new JsonBDRepositorio(); } @Override public boolean inicioSesionUsuario(String nombre, String contrasena) { Usuario usuarioLogin = repositorio.obtenerUsuario(nombre); if(usuarioLogin != null && usuarioLogin.getContrasena().equals(contrasena)) return true; return false; } @Override public boolean altaUsuario(Usuario usuario) { return repositorio.altaUsuario(usuario); } @Override public boolean altaProducto(Producto producto) { return repositorio.altaProducto(producto); } @Override public List<Usuario> listarUsuarios() { return repositorio.obtenerUsuarios(); } @Override public List<Producto> listarProductos() { return repositorio.obtenerProductos(); } @Override public boolean eliminarProducto(String codigo) { return repositorio.eliminarProducto(codigo); } @Override public String nombreServidor() throws RemoteException { //Para depurar System.out.println("Ejecutado método nombreServidor de prueba de conexión en cliente remoto -> OK"); //Sólo para comprobar que se ha establecido la conexión //Y que los métodos remotos se ejecutan //Se le llama desde el cliente, se ejecuta en el servidor //Y devuelve, en este cas, un valor string al cliente return "Servidor_RMI_ProyectoA"; } } |
- Main.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 |
package com.main; import com.main.rmi.TiendaOnlineRMIImpl; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.Scanner; public class Main { private void iniciarServidor(){ int puertoEscuchaRMI; String servidorRMI; Scanner entradaTeclado = new Scanner(System.in); //Solicitamos la IP o el nombre de red del servidor //localhost si es el mismo donde se está ejecutando la aplicación servidor //para el registro RMI System.out.print("Introduzca la IP o nombre DNS del servidor RMI (ej. localhost): "); servidorRMI = entradaTeclado.next(); System.out.print("Introduzca el puerto en el que se ejecutará el registro RMI (ej. 1099): "); puertoEscuchaRMI = entradaTeclado.nextInt(); //Habilitamos la seguridad para iniciar RMI if (System.getSecurityManager() == null) { //Debe existir el fichero java.policy System.setSecurityManager(new SecurityManager()); } try { //Iniciamos el servicio RMI System.setProperty ("java.rmi.server.hostname", servidorRMI); Registry registroRMI = LocateRegistry.createRegistry(puertoEscuchaRMI); String urlUbicacion = "//" + servidorRMI + ":" + puertoEscuchaRMI + "/TiendaOnlineServidor"; Naming.bind(urlUbicacion, registroRMI); registroRMI.rebind("rmi://" + servidorRMI + ":" + puertoEscuchaRMI + "/TiendaOnlineServidor", new TiendaOnlineRMIImpl()); System.out.println("Servidor RMI iniciado en: rmi://" + servidorRMI + ":" + puertoEscuchaRMI + "/TiendaOnlineServidor"); System.out.println("El servidor queda en escucha de clientes RMI." + "\nNo cierre esta consola salvo que quiera detener el servidor"); } catch (RemoteException e) { System.err.println("Error de comunicación al iniciar servidor RMI: " + e.getMessage()); System.exit(1); } catch (Exception e) { System.err.println("Error al iniciar el servidor RMI: " + e.getMessage()); //e.printStackTrace(); System.exit(2); } } //Clase principal, constructor, iniciamos el servidor RMI public static void main(String[] args) { (new Main()).iniciarServidor(); } } |
Crear clases necesarias para la aplicación Cliente en Java RMI
Por otra parte, crearemos un nuevo proyecto que será el cliente, el que conectará mediante RMI al servidor para usar sus métodos remotos. Contendrá una copia del modelo de datos serializado del servidor (Producto y Usuario), para el envío de datos.
El cliente tendrá una implementación de la interfaz remota TiendaOnlineRMI, con los métodos del servidor: inicioSesionUsuario, altaUsuario, listarUsuarios, altaProducto, listarProductos, eliminarProducto y nombreServidor, que será idéntica a la definida en el servodor. En esta implementación NO se desarrollarán los métodos, no tendrán código, únicamente su definición, dado que el código se establece en la parte del servidor.
En la clase principal Cliente, se agregarán todos los métodos para conectar con el servidor (solicitando al usuario la IP y el puerto), se usarán los métodos remotos para el inicio de sesión del usuario en la tienda online y se mostrarán los diferentes menús al usuario: menú principal, gestión de productos y gestión de usuarios.
El cliente tendrá una copia de las clases serializables Producto.java y Usuario.java del servidor, que serán idénticas a las del servidor.
A continuación mostramos el código fuente completo de cada clase descrita anteriormente para la parte del Cliente RMI. Las partes importantes del código van comentadas con su utilidad y explicación.
- Producto.java: idéntico al del servidor.
- Usuario.java: idéntico al del servidor.
- TiendaOnlineRMI.java: idéntico al del servidor.
- Cliente.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 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 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 |
package com.main; import com.main.models.Producto; import com.main.models.Usuario; import com.main.rmi.TiendaOnlineRMI; import java.io.IOException; import java.net.MalformedURLException; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Cliente { TiendaOnlineRMI tiendaOnlineRMI; public static void main(String[] args) throws InterruptedException, RemoteException, MalformedURLException, NotBoundException { Cliente cliente = new Cliente(); cliente.conectarConServidor(); cliente.iniciarConexionServidorRMI(); } private void iniciarConexionServidorRMI() throws InterruptedException, RemoteException{ boolean opcionValida = false; Scanner entradaTeclado = new Scanner(System.in); //Si no está conectado con el servidor RMI realizamos la conexión if (tiendaOnlineRMI != null) { String r; while (!opcionValida) { System.out.print("¿Está registrado en la Tienda Online? (s/n): "); r = entradaTeclado.nextLine(); if (r.equalsIgnoreCase("S")) { opcionValida = true; } else if (r.equalsIgnoreCase("N")) { limpiarPantalla(); altaUsuario(true); opcionValida = true; } else { limpiarPantalla(); System.out.println("Opción incorrecta\n\n"); } } Usuario usuario = obtenerDatosInicioSesion(); boolean iniciadaSesion = false; try { iniciadaSesion = tiendaOnlineRMI.inicioSesionUsuario(usuario.getNombre(), usuario.getContrasena()); } catch (RemoteException ex) { System.out.println("Error al conectar con el servidor RMI: " + ex.getMessage()); System.exit(-1); } if (iniciadaSesion) { Thread.sleep(500); limpiarPantalla(); System.out.println("\nBienvenid@ " + usuario.getNombre()); mostrarMenuPrincipal(); System.out.println("Desconectando del servidor RMI..."); } else { System.out.println("¡Usuario o contraseña incorrectos!"); System.out.println("Se cierra el programa por seguridad..."); } } } //Limpia la consola (para Windows ejecuta cls, para Linux, MAC, ejecuta clear) private void limpiarPantalla() { try { if (System.getProperty("os.name").contains("Windows")) { new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); } else Runtime.getRuntime().exec("clear"); } catch (IOException | InterruptedException e) { System.out.println("Error al limpiar la pantalla: " + e.getMessage()); } } //Conectar con servidor RMI (debe estar iniciado en el puerto que se indique) private void conectarConServidor() throws RemoteException { Registry registroRMI; String puerto = null; String servidor; boolean puertoValido = false; Scanner scan = new Scanner(System.in); System.out.print("Introduzca la IP o nombre DNS del Servidor RMI (ej. localhost): "); servidor = scan.next(); while (!puertoValido) { System.out.print("Introduzca el puerto de escucha del servidor RMI (ej. 1099): "); puerto = scan.next(); if (esNumero(puerto)) puertoValido = true; else System.out.println("El puerto introducido no es válido\n\n"); } System.out.println("Conectando con el servidor RMI..."); registroRMI = LocateRegistry.getRegistry(servidor, Integer.parseInt(puerto)); try { tiendaOnlineRMI = (TiendaOnlineRMI) registroRMI.lookup("rmi://" + servidor + ":" + puerto + "/TiendaOnlineServidor"); //Ejecutamos método remoto en servidor nombreServidor() de prueba de conexión System.out.println("Conectado al servidor RMI [" + tiendaOnlineRMI.nombreServidor() + "] -> OK"); } catch (Exception e) { System.out.println("Error al conectar al servidor RMI: " + e.getMessage()); } } //Mostrar menú principal private void mostrarMenuPrincipal() { Scanner entradaTeclado = new Scanner(System.in); String opcionElegida; System.out.println("******************************"); System.out.println(" TIENDA ONLINE"); System.out.println("******************************"); System.out.println("1 Gestión de productos"); System.out.println("2 Gestión de usuarios"); System.out.println("0 Salir\n"); System.out.print("Introduzca una opción y pulse INTRO: "); opcionElegida = entradaTeclado.nextLine(); switch(opcionElegida) { case "1": limpiarPantalla(); try { mostrarMenuProducto(); } catch (RemoteException e) { System.out.println("Error al abrir menú de Productos: " + e.getMessage()); } break; case "2": limpiarPantalla(); try { mostrarMenuUsuario(); } catch (RemoteException e) { System.out.println("Error al abrir menú de Usuarios: " + e.getMessage()); } break; case "0": System.exit(0); break; default: mostrarMenuPrincipal(); } } //Obtener datos de inicio de sesión (pedir usuario y contraseña) //Devolverá el objeto usuario con los datos introducidos por consola private Usuario obtenerDatosInicioSesion(){ Scanner entradaTeclado = new Scanner(System.in); Usuario usuario = new Usuario(); System.out.println("******************************"); System.out.println(" INICIO DE SESIÓN"); System.out.println("******************************"); System.out.print("Introduzca su usuario: "); usuario.setNombre(entradaTeclado.nextLine()); System.out.print("Introduzca su contraseña: "); usuario.setContrasena(obtenerHahMD5(entradaTeclado.nextLine())); return usuario; } //Según la opción elegida mostrar el menú correspondiente private void irAlMenu(int opcionMenu, boolean directo) throws RemoteException { Scanner entradaTeclado = new Scanner(System.in); //Si no es directo, pedir pulsación de INTRO para continuar if (directo == false) { System.out.print("\nPulse INTRO para continuar "); entradaTeclado.nextLine(); } limpiarPantalla(); switch(opcionMenu) { case 1: mostrarMenuProducto(); break; case 2: mostrarMenuUsuario(); break; default: mostrarMenuPrincipal(); break; } } //Mostrar menú de gestión de usuarios private void mostrarMenuUsuario() throws RemoteException { String opcionElegida; Scanner entradaTeclado = new Scanner(System.in); System.out.println("******************************"); System.out.println(" GESTIÓN DE USUARIOS"); System.out.println("******************************"); System.out.println("1 Alta de usuario"); System.out.println("2 Listado de usuarios"); System.out.println("0 Ir al Menú Principal\n"); System.out.print("Introduzca una opción y pulse INTRO: "); opcionElegida = entradaTeclado.nextLine(); switch(opcionElegida) { case "1": limpiarPantalla(); try { altaUsuario(false); } catch (RemoteException e) { System.out.println("Error al ejecutar método remoto altaUsuario: " + e.getMessage()); } break; case "2": limpiarPantalla(); try { mostrarUsuarios(); } catch (RemoteException e) { System.out.println("Error al ejecutar método remoto mostrarUsuarios: " + e.getMessage()); } break; case "3": eliminarProducto(); break; case "0": mostrarMenuPrincipal(); break; default: limpiarPantalla(); mostrarMenuUsuario(); } } //Validar contraseña del usuario private boolean validarContrasenaUsuario(String s){ Pattern pattern; Matcher matcher; String pwdPattern = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[*&%]).{8,20})"; pattern = Pattern.compile(pwdPattern); matcher = pattern.matcher(s); return matcher.matches(); } //Validar correo electrónico private boolean validarEmail(String s){ Pattern pattern; Matcher matcher; String mailPattern = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; pattern = Pattern.compile(mailPattern); matcher = pattern.matcher(s); return matcher.matches(); } //Alta de nuevo usuario private void altaUsuario(boolean fromLogin) throws RemoteException{ Scanner entradaTeclado = new Scanner(System.in); String nombreUsuario = null, mailConfirmacion, mail = null, contrasena = null, telefono = null; Usuario usuario = new Usuario(); boolean usuarioValido = false; boolean contrasenaValida = false; boolean mailValido = false; boolean mailConfirmacionValido = false; boolean telefonoValido = false; boolean existeUsuario = true; System.out.println("******************************"); System.out.println(" NUEVO USUARIO"); System.out.println("******************************"); while (!usuarioValido) { System.out.print("Introduzca el nombre de usuario: "); nombreUsuario = entradaTeclado.nextLine(); usuarioValido = esAlfanumerica(nombreUsuario); if (!usuarioValido) { System.out.print("Introduzca un nombre de usuario válido (alfanumérico).\n"); } } usuario.setNombre(nombreUsuario); while (!contrasenaValida) { System.out.print("Introduzca la contraseña (mínimo 8 caracteres, " + "mayúsculas y algún carácter especial (*, &, %, .): "); contrasena = entradaTeclado.nextLine(); contrasenaValida = validarContrasenaUsuario(contrasena); if (!contrasenaValida) { System.out.println("La contraseña introducida no cumple los criterios de fortaleza.\n"); } } usuario.setContrasena(obtenerHahMD5(contrasena)); while (!mailValido || !mailConfirmacionValido) { System.out.print("Introduzca la dirección de mail: "); mail = entradaTeclado.nextLine(); mailValido = validarEmail(mail); if (mailValido) { System.out.print("Confirme la dirección de mail: "); mailConfirmacion = entradaTeclado.nextLine(); mailConfirmacionValido = validarEmail(mailConfirmacion); if (!(mailConfirmacionValido && (mail.equals(mailConfirmacion)))) { System.out.println("\nNo coincide el mail con la confirmación.\n"); } } else { System.out.println("La dirección de correo electrónico introducida no es correcta.\n"); } } usuario.setEmail(mail); while (!telefonoValido) { System.out.print("Introduzca el número de teléfono: "); telefono = entradaTeclado.nextLine(); telefonoValido = esNumero(telefono); if (!telefonoValido) { System.out.println("\nEl teléfono introducido no es correcto.\n"); } } usuario.setTelefono(telefono); while (existeUsuario) { existeUsuario = !tiendaOnlineRMI.altaUsuario(usuario); if (existeUsuario) { usuario.setNombre(cambiarNombreUsuario()); } else { limpiarPantalla(); System.out.println("El usuario se ha registrado correctamente."); System.out.println("Podrá iniciar sesión en el programa con este usuario."); } } if (!fromLogin) { irAlMenu(2, false); } } //Nombre de usuario si ya existe en la tienda private String cambiarNombreUsuario(){ Scanner entradaTeclado = new Scanner(System.in); String nombreUsuario = null; boolean nombreUsuarioValido = false; System.out.println("\nEl nombre de usuario elegido ya existe en la tienda. Introduzca uno nuevo.\n"); while (nombreUsuarioValido == false) { System.out.print("Nombre de usuario: "); nombreUsuario = entradaTeclado.nextLine(); nombreUsuarioValido = esAlfanumerica(nombreUsuario); if (!nombreUsuarioValido) { System.out.println("Introduzca un nombre de usuario válido (alfanumérico).\n"); } } return nombreUsuario; } //Mostrar los productos private void mostrarProductos() throws RemoteException { List<Producto> productos = tiendaOnlineRMI.listarProductos(); for (Producto producto : productos) { System.out.println("\n**********************"); System.out.println("Código: " + producto.getCodigo()); System.out.println("Nombre: " + producto.getNombre()); System.out.println("Precio: " + producto.getPrecio()); } //Una vez mostrados esperamos la pulsación de INTRO y volvemos al menú de productos irAlMenu(1, false); } //Mostrar los usuarios private void mostrarUsuarios() throws RemoteException { List<Usuario> usuarios = tiendaOnlineRMI.listarUsuarios(); for (Usuario usuario : usuarios) { System.out.println("\n**********************"); System.out.println("Nombre: " + usuario.getNombre()); System.out.println("Email: " + usuario.getEmail()); System.out.println("Teléfono: " + usuario.getTelefono()); } //Una vez mostrados esperamos la pulsación de INTRO y volvemos al menú de usuarios irAlMenu(2, false); } //Mostrar menú de gestión de productos //Devolverá la opción elegida por el usuario private void mostrarMenuProducto() throws RemoteException { String opcionElegida; Scanner entradaTeclado = new Scanner(System.in); System.out.println("******************************"); System.out.println(" GESTIÓN DE PRODUCTOS"); System.out.println("******************************"); System.out.println("1 Mostrar productos"); System.out.println("2 Nuevo producto"); System.out.println("3 Eliminar producto"); System.out.println("0 Ir al Menú Principal\n"); System.out.print("Introduzca una opción y pulse INTRO: "); opcionElegida = entradaTeclado.nextLine(); switch(opcionElegida) { case "1": limpiarPantalla(); mostrarProductos(); break; case "2": limpiarPantalla(); altaProducto(); break; case "3": limpiarPantalla(); eliminarProducto(); break; case "0": limpiarPantalla(); mostrarMenuPrincipal(); break; default: limpiarPantalla(); mostrarMenuProducto(); } } //Añadir producto private void altaProducto() throws RemoteException{ String codigo, precioSTR, nombre; double precio; Scanner entradaTeclado = new Scanner(System.in); System.out.println("******************************"); System.out.println(" AÑADIR PRODUCTO"); System.out.println("******************************"); System.out.print("Código del producto: "); codigo = entradaTeclado.nextLine(); System.out.print("Nombre del producto: "); nombre = entradaTeclado.nextLine(); System.out.print("Precio de venta: "); precioSTR = entradaTeclado.nextLine(); precioSTR.replace(".", ","); while (!esDouble(precioSTR)) { System.out.println("Introduzca un precio correcto."); System.out.print("Precio de venta: "); precioSTR = entradaTeclado.nextLine(); precioSTR.replace(",", "."); } precio = Double.parseDouble(precioSTR); Producto producto = new Producto(); producto.setNombre(nombre); producto.setPrecio((float)precio); producto.setCodigo(codigo); boolean existeElCodigo = true; while (existeElCodigo) { existeElCodigo = !tiendaOnlineRMI.altaProducto(producto); if (!existeElCodigo) { System.out.println("\nEl producto se ha registrado correctamente en la tienda."); irAlMenu(1, false); } else { System.out.println("\nEl código del producto ya existe en la tienda."); System.out.println("El producto NO se ha añadido."); irAlMenu(1, false); } } } //Eliminar producto private void eliminarProducto() throws RemoteException { Scanner entradaTeclado = new Scanner(System.in); String codigoProducto = null; boolean codigoValido = false; System.out.println("******************************"); System.out.println(" ELIMINAR PRODUCTO"); System.out.println("******************************"); while (!codigoValido) { System.out.print("Introduzca el código del producto que desea eliminar: "); codigoProducto = entradaTeclado.nextLine(); if (codigoProducto != null) { codigoValido = true; } else { System.out.println("Debe introducir un código de producto\n"); } } if (tiendaOnlineRMI.eliminarProducto(codigoProducto)) { limpiarPantalla(); System.out.println("El producto ha sido eliminado correctamente\n"); irAlMenu(1, false); } else { System.out.print("\nEl producto indicado NO existe en la tienda\n"); irAlMenu(1, false); } } //Comprobar si una cadena es alfanumérica private boolean esAlfanumerica(String s){ return s.matches("[a-zA-Z_0-9]+"); } //Comprobar si un valor es double (para el precio del producto) private boolean esDouble(String str) { try { Double.parseDouble(str); return true; } catch (NumberFormatException e) { return false; } } //Comprobar si un valor es numérico (para el puerto de conexión con servidor RMI) private boolean esNumero(String s){ return s.matches("[0-9]*"); } //Cifrar texto en hash MD5 (para contraseña del usuario) private String obtenerHahMD5(String contrasena) { final byte[] defaultBytes = contrasena.getBytes(); try { final MessageDigest md5MsgDigest = MessageDigest.getInstance("MD5"); md5MsgDigest.reset(); md5MsgDigest.update(defaultBytes); final byte messageDigest[] = md5MsgDigest.digest(); final StringBuffer hexString = new StringBuffer(); for (final byte element : messageDigest) { final String hex = Integer.toHexString(0xFF & element); hexString.append(hex); } contrasena = hexString + ""; } catch (final NoSuchAlgorithmException e) { System.out.println("Error al obtener hash MD5: " + e.getMessage()); //e.printStackTrace(); } return contrasena; } } |
Compilar el cliente y el servidor desde IntelliJ IDEA para generar los .class y ejecutar en sistema operativo
Compilar aplicaciones servidor y cliente para generar los .class
Para probar las aplicaciones servidor y cliente las compilaremos desde IntelliJ IDEA (o cualquier otro compilador Java) y generaremos los .class. Para ello, abriremos el proyecto Servidor RMI, editaremos la configuración de la aplicación:
Revisaremos los datos de configuración y compilación de la aplicación. Anotaremos la versión de java que usaremos para la compilación, dado que en los equipos donde ejecutemos la aplicación deberemos tener una versión igual o superior:
Una vez revisado todo y establecida la clase principal Main, compilaremos el proyecto, desde el menú «Build» – «Build Project»:
Nos habrá generado los .class correspondientes en la carpeta …out\production…:
Realizaremos el mismo proceso para la aplicación cliente, que nos generará sus correspondientes .class:
Ejecutar aplicación Servidor RMI
Para la ejecución en otro equipo, copiaremos cada carpeta TiendaOnlineClientePruebas y TiendaOnlineServer los equipos en los que queramos ejecutar el servidor y el cliente, pueden estar en el mismo equipo o en diferentes equipos. Lo habitual en una arquitectura cliente/servidor, en una aplicación de producción, es que el programa que será el servidor RMI se encuentre en un equipo y el resto de equipos ejecutarán el programa cliente para conectarse al servidor. Será en el servidor donde se almacenen los datos (persistencia), en nuestro caso los fichreos usuarios.json y productos.json:
El equipo servidor, si optamos por un equipo con sistema operativo Windows, tendremos que abrir el puerto que usemos para RMI en el cortafuegos de Windows (o el cortafuegos del sistema antivirus que usemos):
Si ejecutamos el servidor en un equipo Linux, también tendremos que abrir el puerto RMI que usemos en el cortafuegos. Por ejemplo usando el comando:
firewall-cmd –zone=public –add-port=1099/tcp –permanent
(variará en función de la distribución de Linux y del sistema de cortafuegos que usemos)
Para ejecutar el servidor desde la línea de comandos (fuera del IDE IntellyJ IDEA) seguiremos los siguientes pasos. En primer lugar nos aseguraremos de disponer de la carpeta:
… Servidor\out\production\TiendaOnlineServer\com\google\gson
Dicha carpeta contendrá los ficheros tras descomprimir el JAR de Gson gson-2.2.2.jar. Todos los ficheros se incluyen en la descarga del ejemplo de este artículo.
Para ejecutar el servidor, en un equipo Windows, desde una ventana de consola MS-DOS, accederemos a la carpeta donde tengamos la versión de Java que queramos usar para ejecutar la aplicación, en nuestro caso en la carpeta:
cd C:\Users\alonso\.jdks\openjdk-15.0.1-1\bin
La versión de Java debe ser igual o superior a la que se usó para generar los .class. Ejecutaremos el siguiente comando para abrir la aplicación servidor:
java.exe -Djava.security.manager -Djava.security.policy=D:\ProyectoA_Java\ProyectoA_Tienda_Online_Java_RMI\Servidor\java.policy -classpath D:\ProyectoA_Java\ProyectoA_Tienda_Online_Java_RMI\Servidor\out\production\TiendaOnlineServer com.main.Main
Teniendo en cuenta que en nuestro caso la aplicación servidor se encuentra en la carpeta:
D:\ProyectoA_Java\ProyectoA_Tienda_Online_Java_RMI\Servidor
Donde también se incluye el fichero java.policy con el siguiente contenido:
1 2 3 |
grant { permission java.security.AllPermission; }; |
Si todo es correcto se iniciará la aplicación servidor, nos solicitará una IP o nombre DNS (normalmente la misma IP del equipo donde lo estamos ejecutando) y un puerto (el defecto para RMI es el 1099). Introduciremos estos datos y la aplicación iniciará el servicio RMI y quedará a la espera de conexión de clientes. NO recomendamos usar «localhost» para la IP, dado que los clientes necesitarán registrar la IP o nombre DNS del equipo servidor y si usamos «localhost» no lo encontrarán. Por ello, aunque el registro RMI se haga en el mismo equipo desde el que se ejecuta la aplicación servidor (que es lo normal), introduciremos su IP y NO «localhost», en nuestro caso 192.168.1.2:
Si el puerto establecido está libre y el servirio Registry RMI de Java se ha ejecutado correctamente, se iniciará la aplicación servidor y nos mostrará el log de su ejecución en la consola, como vemos en la imagen anterior:
1 2 3 4 5 6 7 8 9 10 11 |
Introduzca la IP o nombre DNS del servidor RMI (ej. localhost): 192.168.1.2 Introduzca el puerto en el que se ejecutará el registro RMI (ej. 1099): 1099 Creando clase GSON de Google para persistencia en JSON... Creada clase GSON de Google para persistencia en JSON -> OK Obteniendo listado de usuarios desde la persistencia JSON... usuarios.json Obteniendo listado de productos desde la persistencia JSON... productos.json Servidor RMI iniciado en: rmi://192.168.1.2:1099/TiendaOnlineServidor El servidor queda en escucha de clientes RMI. No cierre esta consola salvo que quiera detener el servidor |
Ejecutar aplicación Cliente RMI
Para el caso de la aplicación cliente, al igual que para el servidor, nos aseguraremos de disponer de la misma versión de Java o superior, y ejecutaremos el siguiente comando (desde una ventana de MS-DOS si estamos en un equipo Windows o desde la línea de comandos de una ventana de terminal si estamos en Linux o macOS). Para equipos Windows ejecutaríamos:
java.exe -Djava.security.manager -Djava.security.policy=D:\ProyectoA_Java\ProyectoA_Tienda_Online_Java_RMI\Cliente\java.policy -classpath D:\ProyectoA_Java\ProyectoA_Tienda_Online_Java_RMI\Cliente\out\production\TiendaOnlineClientePruebas com.main.Cliente
Teniendo en cuenta que en nuestro caso la aplicación cliente se encuentra en la carpeta:
D:\ProyectoA_Java\ProyectoA_Tienda_Online_Java_RMI\Cliente
El fichero java.policy también debe existir en la carpeta indicada y con el mismo contenido que el de la aplicación servidor.
Para equipos Linux o macOS ejecutaremos:
java -Djava.security.manager -Djava.security.policy=/tmp/TiendaOnlineClientePruebas/java.policy -classpath /tmp/TiendaOnlineClientePruebas com.main.Cliente
Teniendo en cuenta que la carpeta de la aplicación cliente se encuentra en /tmp/TiendaOnlineClientePruebas.
Si la aplicación cliente se ejecuta correctamente, nos solicitará la IP del servidor y su puerto de conexión. Introduciremos estos datos. Si conecta con el servidor nos preguntará si tenemos usuario registrado. Si respondemos «S» (sí) nos pedirá el usuario y la contraseña, si repondemos «N» (no) nos llevará al menú de alta de nuevo usuario. Más adelante mostramos el funcionamiento completo de la aplicación:
Vemos que gracias a la versatilidad de Java, la aplicación puede funcionar en casi cualquier sistema operativo. Es indiferente desde qué sistema operativo ejecutemos tanto el servidor como el cliente. En el equipo hemos ejecutado el servidor en Windows y un cliente en Linux, funcionando perfectamente.
Descarga del código fuente completo en Java con el IDE IntelliJ IDEA
A continuación dejamos enlace para la descarga completa tanto del Cliente como del Servidor (desarrolado en Java con el IDE IntelliJ IDEA):
La aplicación Tienda Online Java RMI en funcionamiento
Vamos a mostrar el funcionamiento completo de la aplicación cliente/servidor (programación distribuida) en Java RMI.
La parte del servidor es bastante sencilla, como hemos visto anteriormente, únicamente nos solicitará la IP y el puerto y quedará a la espera de conexiones de clientes. Además, desde la consola del servidor, se irán mostrando datos de acciones que los clientes van realizado (a modo de depuración):
Y en el servidor, en la carpeta …TiendaOnlineServer\com\main\bd, se almacenarán los ficheros de la persistencia de los obejetos serializables productos y usuarios:
La aplicación cliente, al ejecutarla, nos solicitará IP y puerto del servidor, como hemos indicado anteriormente. Una vez conectados, nos preguntará si tenemos ususuario registrado. Si lo tenemos, nos solicitará usuario y contraseña para iniciar sesión. Si las credenciales son correctas nos mostrará el menú principal de la aplicación:
Si no tenemos usuario registrado, nos mostrará el menú de alta de usuario. Desde este menú nos solicitará un nombre de usuario (comprobará que sea alfanumérico), una contraseña (comprobará que tenga una cierta fortaleza), una dirección de correo electrónico (comprobará que esté bien formada), la confirmación de la dirección de correo y un teléfono. Si el nombre de usuario no existen en la base de datos nos creará este nuevo usuario y nos llevará al menú de inicio de sesión:
Desde el menú principal, si pulsamos 1 e INTRO, nos llevará al menú de gestión de productos:
Desde aquí podremos mostrar todos los productos de la base de datos, pulsando «1»:
Eligiendo la opción «2» podremos añadir un nuevo producto a la tienda:
Y eligiendo la opción «3» podremos eliminar un producto existente (sabiendo su código):
Para los usuarios tendremos las mismas opciones que para los productos, salvo la de eliminar:
Con la opción «1» la aplicación nos permitirá dar de alta un nuevo usuario para inicio de sesión en el sistema. En caso de existir un usuario con el mismo nombre, la aplicación nos solicitará un nombre de usuario, hasta que esté «libre»:
Y también podremos consultar el listado de usuarios de la tienda:
Algunos errores y su posible solución
Si al intentar ejecutar la aplicación (sea la cliente o la servidor) aparece el siguiente error:
Error: Se ha producido un error de enlace al cargar la clase principal com.main.Cliente
java.lang.UnsupportedClassVersionError: com/main/Cliente has been compiled by a more recent version of the Java Runtime (class file version 59.0), this version of the Java Runtime only recognizes class file versions up to 55.0
Habrá que instalar una versión de Java superior a la que tenemos en el equipo. Por ejemplo, para un equipo con Linux CentOS 7 (válido para Red Hat o Fedora) seguiremos estos pasos para actualizar la versión de Java:
1º para comprobar la versión actual de Java podemos ejecutar el comando:
java –version
2º Para actualizar el sistema podemos ejecutar el comando
yum -y update
3º Para descargar la versión jdk 16.0.2 podemos ejecutar el comando:
wget –no-check-certificate -c –header «Cookie: oraclelicense=accept-securebackup-cookie» https://download.oracle.com/otn-pub/java/jdk/16.0.2+7/d4a915d82b4c4fbb9bde534da945d746/jdk-16.0.2_linux-x64_bin.rpm
4º Para instalar el rmp descargado podemos ejecutar el comando:
rpm -ivh jdk-16.0.2_linux-x64_bin.rpm
5º Y por último, para establecer la nueva versión de Java instalada como la versión principal a usar en el sistema, podemos ejecutar el comando:
alternatives –config java
Elegiremos la versión instalada en /usr/java/jdk-16.0.2/bin/java como la versión principal. Pero antes de elegir esta versión, conviene que nos aseguremos de que ninguna otra aplicación Java de nuestro equipo requiera la versión anterior para su funcionamiento.