[DEV] review many models and system

This commit is contained in:
Edouard DUPIN 2023-10-14 12:18:36 +02:00
parent 99cca8bebf
commit d8c6de7bde
79 changed files with 3367 additions and 2996 deletions

7
.checkstyle Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
<fileset name="all" enabled="true" check-config-name="Google Checks" local="false">
<file-match-pattern match-pattern="." include-pattern="true"/>
</fileset>
</fileset-config>

View File

@ -8,36 +8,20 @@
</classpathentry> </classpathentry>
<classpathentry kind="src" output="out/maven/test-classes" path="test/src"> <classpathentry kind="src" output="out/maven/test-classes" path="test/src">
<attributes> <attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/> <attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="out/maven/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/> <attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="src" output="out/maven/test-classes" path="out/maven/generated-test-sources/test-annotations"> <classpathentry exported="true" kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes> <attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/> </attributes>
<attribute name="m2e-apt" value="true"/> </classpathentry>
<attribute name="test" value="true"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<attributes>
<attribute name="module" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="output" path="out/maven/classes"/> <classpathentry kind="output" path="out/maven/classes"/>

1
conditioningUnit Normal file
View File

@ -0,0 +1 @@
[]

13
pom.xml
View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.3.8</version> <version>0.4.0</version>
<properties> <properties>
<jersey.version>3.1.1</jersey.version> <jersey.version>3.1.1</jersey.version>
<jaxb.version>2.3.1</jaxb.version> <jaxb.version>2.3.1</jaxb.version>
@ -130,6 +130,17 @@
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
<version>9.30</version> <version>9.30</version>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -16,3 +16,4 @@ public class GlobalConfiguration {
ConfigBaseVariable.getDBKeepConnected()); ConfigBaseVariable.getDBKeepConnected());
} }
} }

View File

@ -5,14 +5,15 @@ import org.kar.archidata.util.JWTWrapper;
public class UpdateJwtPublicKey extends Thread { public class UpdateJwtPublicKey extends Thread {
boolean kill = false; boolean kill = false;
public void run() { public void run() {
if (ConfigBaseVariable.getSSOAddress() == null) { if (ConfigBaseVariable.getSSOAddress() == null) {
System.out.println("SSO INTERFACE is not provided ==> work alone."); System.out.println("SSO INTERFACE is not provided ==> work alone.");
// No SO provided, kill the thread. // No SO provided, kill the thread.
return; return;
} }
while (this.kill == false) { while (this.kill == false) {
// need to upgrade when server call us... // need to upgrade when server call us...
try { try {
JWTWrapper.initLocalTokenRemote(ConfigBaseVariable.getSSOAddress(), "archidata"); JWTWrapper.initLocalTokenRemote(ConfigBaseVariable.getSSOAddress(), "archidata");
} catch (Exception e1) { } catch (Exception e1) {
@ -22,12 +23,13 @@ public class UpdateJwtPublicKey extends Thread {
} }
try { try {
// update every 5 minutes the master token // update every 5 minutes the master token
Thread.sleep(1000*60*5, 0); Thread.sleep(1000 * 60 * 5, 0);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
public void kill() { public void kill() {
this.kill = true; this.kill = true;
} }

View File

@ -1,45 +1,43 @@
package org.kar.archidata; package org.kar.archidata;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.model.User;
import org.kar.archidata.sqlWrapper.SqlWrapper;
import java.io.IOException; import java.io.IOException;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.model.User;
import org.kar.archidata.sqlWrapper.SqlWrapper;
public class UserDB { public class UserDB {
public UserDB() { public UserDB() {}
}
public static User getUsers(long userId) throws Exception {
public static User getUsers(long userId) throws Exception { return SqlWrapper.get(User.class, userId);
return SqlWrapper.get(User.class, userId); }
}
public static User getUserOrCreate(long userId, String userLogin) throws Exception {
User user = getUsers(userId);
public static User getUserOrCreate(long userId, String userLogin) throws Exception { if (user != null) {
User user = getUsers(userId); return user;
if (user != null) { }
return user; createUsersInfoFromOAuth(userId, userLogin);
} return getUsers(userId);
createUsersInfoFromOAuth(userId, userLogin); }
return getUsers(userId);
} private static void createUsersInfoFromOAuth(long userId, String login) throws IOException {
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
private static void createUsersInfoFromOAuth(long userId, String login) throws IOException { String query = "INSERT INTO `user` (`id`, `login`, `lastConnection`, `admin`, `blocked`, `removed`) VALUE (?,?,now(3),'0','0','0')";
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig); try {
String query = "INSERT INTO `user` (`id`, `login`, `lastConnection`, `admin`, `blocked`, `removed`) VALUE (?,?,now(3),'0','0','0')"; PreparedStatement ps = entry.connection.prepareStatement(query);
try { ps.setLong(1, userId);
PreparedStatement ps = entry.connection.prepareStatement(query); ps.setString(2, login);
ps.setLong(1, userId); ps.executeUpdate();
ps.setString(2, login); } catch (SQLException throwables) {
ps.executeUpdate(); throwables.printStackTrace();
} catch (SQLException throwables) { } finally {
throwables.printStackTrace(); entry.close();
} finally { }
entry.close(); }
}
}
} }

View File

@ -0,0 +1,129 @@
package org.kar.archidata.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Table;
public class AnnotationTools {
static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class);
public static String getTableName(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Table.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @SQLTableName on " + element.getClass().getCanonicalName());
}
final String tmp = ((Table) annotation[0]).name();
if (tmp == null) {
return null;
}
return tmp;
}
public static String getComment(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(SQLComment.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @SQLComment on " + element.getClass().getCanonicalName());
}
final String tmp = ((SQLComment) annotation[0]).value();
if (tmp == null) {
return null;
}
return tmp;
}
public static String getDefault(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(SQLDefault.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @SQLDefault on " + element.getClass().getCanonicalName());
}
final String tmp = ((SQLDefault) annotation[0]).value();
if (tmp == null) {
return null;
}
return tmp;
}
public static Integer getLimitSize(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @SQLLimitSize on " + element.getClass().getCanonicalName());
}
return ((Column) annotation[0]).length();
}
public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) {
try {
final Annotation[] anns = field.getAnnotations();
for (final Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return true;
}
}
for (final Annotation ann : anns) {
final Annotation[] anns2 = ann.annotationType().getDeclaredAnnotations();
for (final Annotation ann2 : anns2) {
if (ann2.annotationType() == annotationType) {
return true;
}
}
}
} catch (final Exception ex) {
LOGGER.error("Catch exception when try to get annotation...{}", ex.getLocalizedMessage());
return false;
}
return false;
}
public static boolean getNotNull(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return true;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
return ((Column) annotation[0]).nullable();
}
public static boolean isPrimaryKey(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return true;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
return ((Column) annotation[0]).unique();
}
public static GenerationType getStrategy(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(GeneratedValue.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
return ((GeneratedValue) annotation[0]).strategy();
}
}

View File

@ -7,6 +7,6 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLCreateTime { public @interface CreationTimestamp {
} }

View File

@ -5,8 +5,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLPrimaryKey { public @interface SQLAddOn {
} }

View File

@ -8,7 +8,7 @@ import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.FIELD }) @Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLComment { public @interface SQLComment {
String value(); String value();
} }

View File

@ -8,7 +8,7 @@ import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.FIELD }) @Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLDefault { public @interface SQLDefault {
String value(); String value();
} }

View File

@ -8,5 +8,5 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLDeleted { public @interface SQLDeleted {
} }

View File

@ -8,5 +8,5 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLIfNotExists { public @interface SQLIfNotExists {
} }

View File

@ -1,12 +0,0 @@
package org.kar.archidata.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLLimitSize {
int value();
}

View File

@ -8,5 +8,5 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLNotRead { public @interface SQLNotRead {
} }

View File

@ -1,14 +0,0 @@
package org.kar.archidata.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLTableName {
String value();
}

View File

@ -1,12 +0,0 @@
package org.kar.archidata.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLUpdateTime {
}

View File

@ -7,6 +7,6 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLAutoIncrement { public @interface UpdateTimestamp {
} }

View File

@ -1,12 +1,15 @@
package org.kar.archidata.annotation; package org.kar.archidata.annotation.addOn;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.kar.archidata.annotation.SQLAddOn;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLNotNull { @SQLAddOn
public @interface SQLTableExternalForeinKeyAsList {
} }

View File

@ -1,32 +1,28 @@
package org.kar.archidata.annotation; package org.kar.archidata.annotation.addOn;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.kar.archidata.annotation.SQLAddOn;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLTableLinkGeneric { @SQLAddOn
public static String AUTOMATIC ="__auto__"; public @interface SQLTableExternalLink {
public enum ModelLink { public static String AUTOMATIC = "__auto__";
NONE,
INTERNAL, // The list is serialized in a string ',' separated // If automatic table name, the table name is: parentTableName_externalTableName__link
EXTERNAL // an external table is created and
};
// Permit to select the link mode.
ModelLink value() default ModelLink.EXTERNAL;
// If automatic table name, the table name is: parentTableName_externalTableName__link
String tableName() default AUTOMATIC; String tableName() default AUTOMATIC;
// If automatic table name, the name of the foreign table is manage with the variable name. // If automatic table name, the name of the foreign table is manage with the variable name.
String externalTableName() default AUTOMATIC; String externalTableName() default AUTOMATIC;
// If the external link have a field to filter with a specific value (name of the field) // If the external link have a field to filter with a specific value (name of the field)
String filterField() default AUTOMATIC; String filterField() default AUTOMATIC;
// If the external link have a field to filter with a specific value (value of the field) // If the external link have a field to filter with a specific value (value of the field)
String filterValue() default AUTOMATIC; String filterValue() default AUTOMATIC;
} }

View File

@ -1,15 +1,14 @@
package org.kar.archidata.annotation.security; package org.kar.archidata.annotation.security;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import jakarta.ws.rs.NameBinding; import jakarta.ws.rs.NameBinding;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@NameBinding @NameBinding
@Retention(RUNTIME) @Retention(RUNTIME)
@Target({METHOD}) @Target({ METHOD })
public @interface DenyAll { public @interface DenyAll {}
}

View File

@ -1,14 +1,14 @@
package org.kar.archidata.annotation.security; package org.kar.archidata.annotation.security;
import jakarta.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import jakarta.ws.rs.NameBinding;
@NameBinding @NameBinding
@Retention(RUNTIME) @Retention(RUNTIME)
@Target({METHOD}) @Target({ METHOD })
public @interface PermitAll { public @interface PermitAll {}
}

View File

@ -1,14 +1,14 @@
package org.kar.archidata.annotation.security; package org.kar.archidata.annotation.security;
import jakarta.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import jakarta.ws.rs.NameBinding;
@NameBinding @NameBinding
@Retention(RUNTIME) @Retention(RUNTIME)
@Target({METHOD}) @Target({ METHOD })
public @interface PermitTokenInURI { public @interface PermitTokenInURI {}
}

View File

@ -1,15 +1,16 @@
package org.kar.archidata.annotation.security; package org.kar.archidata.annotation.security;
import jakarta.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import jakarta.ws.rs.NameBinding;
@NameBinding @NameBinding
@Retention(RUNTIME) @Retention(RUNTIME)
@Target({METHOD}) @Target({ METHOD })
public @interface RolesAllowed { public @interface RolesAllowed {
String[] value(); String[] value();
} }

View File

@ -1,12 +1,32 @@
package org.kar.archidata.api; package org.kar.archidata.api;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import javax.imageio.ImageIO;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam; import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.filter.GenericContext;
import org.kar.archidata.model.Data;
import org.kar.archidata.sqlWrapper.SqlWrapper;
import org.kar.archidata.annotation.security.PermitTokenInURI; import org.kar.archidata.annotation.security.PermitTokenInURI;
import org.kar.archidata.annotation.security.RolesAllowed; import org.kar.archidata.annotation.security.RolesAllowed;
import org.kar.archidata.filter.GenericContext;
import org.kar.archidata.model.Data;
import org.kar.archidata.sqlWrapper.QuerryCondition;
import org.kar.archidata.sqlWrapper.SqlWrapper;
import org.kar.archidata.util.ConfigBaseVariable; import org.kar.archidata.util.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -28,413 +48,376 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.StreamingOutput; import jakarta.ws.rs.core.StreamingOutput;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
// https://stackoverflow.com/questions/35367113/jersey-webservice-scalable-approach-to-download-file-and-reply-to-client // https://stackoverflow.com/questions/35367113/jersey-webservice-scalable-approach-to-download-file-and-reply-to-client
// https://gist.github.com/aitoroses/4f7a2b197b732a6a691d // https://gist.github.com/aitoroses/4f7a2b197b732a6a691d
@Path("/data") @Path("/data")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public class DataResource { public class DataResource {
static final Logger logger = LoggerFactory.getLogger(MediaType.class); private static final Logger LOGGER = LoggerFactory.getLogger(MediaType.class);
private final static int CHUNK_SIZE = 1024 * 1024; // 1MB chunks private final static int CHUNK_SIZE = 1024 * 1024; // 1MB chunks
private final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks private final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks
/** /**
* Upload some datas * Upload some datas
*/ */
private static long tmpFolderId = 1; private static long tmpFolderId = 1;
private static void createFolder(String path) throws IOException { private static void createFolder(String path) throws IOException {
if (!Files.exists(java.nio.file.Path.of(path))) { if (!Files.exists(java.nio.file.Path.of(path))) {
//Log.print("Create folder: " + path); //Log.print("Create folder: " + path);
Files.createDirectories(java.nio.file.Path.of(path)); Files.createDirectories(java.nio.file.Path.of(path));
} }
} }
public static long getTmpDataId() { public static long getTmpDataId() {
return tmpFolderId++; return tmpFolderId++;
} }
public static String getTmpFileInData(long tmpFolderId) { public static String getTmpFileInData(long tmpFolderId) {
String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId; String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId;
try { try {
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return filePath; return filePath;
} }
public static String getFileData(long tmpFolderId) { public static String getFileData(long tmpFolderId) {
String filePath = ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator + "data"; String filePath = ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator + "data";
try { try {
createFolder(ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator); createFolder(ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return filePath; return filePath;
} }
public static Data getWithSha512(String sha512) { public static Data getWithSha512(String sha512) {
logger.info("find sha512 = {}", sha512); LOGGER.info("find sha512 = {}", sha512);
try { try {
return SqlWrapper.getWhere(Data.class, "sha512", "=", sha512); return SqlWrapper.getWhere(Data.class, new QuerryCondition("sha512", "=", sha512));
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
public static Data getWithId(long id) { public static Data getWithId(long id) {
logger.info("find id = {}", id); LOGGER.info("find id = {}", id);
try { try {
return SqlWrapper.get(Data.class, id); return SqlWrapper.get(Data.class, id);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
public static Data createNewData(long tmpUID, String originalFileName, String sha512) throws IOException { public static Data createNewData(long tmpUID, String originalFileName, String sha512) throws IOException {
// determine mime type: // determine mime type:
Data injectedData = new Data(); Data injectedData = new Data();
String mimeType = ""; String mimeType = "";
String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1); String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
switch (extension.toLowerCase()) { switch (extension.toLowerCase()) {
case "jpg": case "jpg":
case "jpeg": case "jpeg":
mimeType = "image/jpeg"; mimeType = "image/jpeg";
break; break;
case "png": case "png":
mimeType = "image/png"; mimeType = "image/png";
break; break;
case "webp": case "webp":
mimeType = "image/webp"; mimeType = "image/webp";
break; break;
case "mka": case "mka":
mimeType = "audio/x-matroska"; mimeType = "audio/x-matroska";
break; break;
case "mkv": case "mkv":
mimeType = "video/x-matroska"; mimeType = "video/x-matroska";
break; break;
case "webm": case "webm":
mimeType = "video/webm"; mimeType = "video/webm";
break; break;
default: default:
throw new IOException("Can not find the mime type of data input: '" + extension + "'"); throw new IOException("Can not find the mime type of data input: '" + extension + "'");
} }
injectedData.mimeType = mimeType; injectedData.mimeType = mimeType;
injectedData.sha512 = sha512; injectedData.sha512 = sha512;
String tmpPath = getTmpFileInData(tmpUID); String tmpPath = getTmpFileInData(tmpUID);
injectedData.size = Files.size(Paths.get(tmpPath)); injectedData.size = Files.size(Paths.get(tmpPath));
try { try {
injectedData = SqlWrapper.insert(injectedData); injectedData = SqlWrapper.insert(injectedData);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
String mediaPath = getFileData(injectedData.id); String mediaPath = getFileData(injectedData.id);
logger.info("src = {}", tmpPath); LOGGER.info("src = {}", tmpPath);
logger.info("dst = {}", mediaPath); LOGGER.info("dst = {}", mediaPath);
Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE); Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE);
logger.info("Move done"); LOGGER.info("Move done");
return injectedData; return injectedData;
} }
public static String saveTemporaryFile(InputStream uploadedInputStream, long idData) { public static String saveTemporaryFile(InputStream uploadedInputStream, long idData) {
return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData)); return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData));
} }
public static void removeTemporaryFile(long idData) { public static void removeTemporaryFile(long idData) {
String filepath = DataResource.getTmpFileInData(idData); String filepath = DataResource.getTmpFileInData(idData);
if (Files.exists(Paths.get(filepath))) { if (Files.exists(Paths.get(filepath))) {
try { try {
Files.delete(Paths.get(filepath)); Files.delete(Paths.get(filepath));
} catch (IOException e) { } catch (IOException e) {
logger.info("can not delete temporary file : {}", Paths.get(filepath)); LOGGER.info("can not delete temporary file : {}", Paths.get(filepath));
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
// save uploaded file to a defined location on the server // save uploaded file to a defined location on the server
static String saveFile(InputStream uploadedInputStream, String serverLocation) { static String saveFile(InputStream uploadedInputStream, String serverLocation) {
String out = ""; String out = "";
try { try {
OutputStream outpuStream = new FileOutputStream(new File( OutputStream outpuStream = new FileOutputStream(new File(serverLocation));
serverLocation)); int read = 0;
int read = 0; byte[] bytes = new byte[CHUNK_SIZE_IN];
byte[] bytes = new byte[CHUNK_SIZE_IN]; MessageDigest md = MessageDigest.getInstance("SHA-512");
MessageDigest md = MessageDigest.getInstance("SHA-512");
outpuStream = new FileOutputStream(new File(serverLocation));
outpuStream = new FileOutputStream(new File(serverLocation)); while ((read = uploadedInputStream.read(bytes)) != -1) {
while ((read = uploadedInputStream.read(bytes)) != -1) { //logger.info("write {}", read);
//logger.info("write {}", read); md.update(bytes, 0, read);
md.update(bytes, 0, read); outpuStream.write(bytes, 0, read);
outpuStream.write(bytes, 0, read); }
} LOGGER.info("Flush input stream ... {}", serverLocation);
logger.info("Flush input stream ... {}", serverLocation); System.out.flush();
System.out.flush(); outpuStream.flush();
outpuStream.flush(); outpuStream.close();
outpuStream.close(); // create the end of sha512
// create the end of sha512 byte[] sha512Digest = md.digest();
byte[] sha512Digest = md.digest(); // convert in hexadecimal
// convert in hexadecimal out = bytesToHex(sha512Digest);
out = bytesToHex(sha512Digest); uploadedInputStream.close();
uploadedInputStream.close(); } catch (IOException ex) {
} catch (IOException ex) { LOGGER.info("Can not write in temporary file ... ");
logger.info("Can not write in temporary file ... "); ex.printStackTrace();
ex.printStackTrace(); } catch (NoSuchAlgorithmException ex) {
} catch (NoSuchAlgorithmException ex) { LOGGER.info("Can not find sha512 algorithms");
logger.info("Can not find sha512 algorithms"); ex.printStackTrace();
ex.printStackTrace(); }
} return out;
return out; }
}
public static String bytesToHex(byte[] bytes) {
public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder();
StringBuilder sb = new StringBuilder(); for (byte b : bytes) {
for (byte b : bytes) { sb.append(String.format("%02x", b));
sb.append(String.format("%02x", b)); }
} return sb.toString();
return sb.toString(); }
}
public Data getSmall(Long id) {
try {
public Data getSmall(Long id) {
try {
return SqlWrapper.get(Data.class, id); return SqlWrapper.get(Data.class, id);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
@POST @POST
@Path("/upload/") @Path("/upload/")
@Consumes({MediaType.MULTIPART_FORM_DATA}) @Consumes({ MediaType.MULTIPART_FORM_DATA })
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public Response uploadFile(@Context SecurityContext sc, @FormDataParam("file") InputStream fileInputStream, @FormDataParam("file") FormDataContentDisposition fileMetaData) { public Response uploadFile(@Context SecurityContext sc, @FormDataParam("file") InputStream fileInputStream, @FormDataParam("file") FormDataContentDisposition fileMetaData) {
GenericContext gc = (GenericContext) sc.getUserPrincipal(); GenericContext gc = (GenericContext) sc.getUserPrincipal();
logger.info("==================================================="); LOGGER.info("===================================================");
logger.info("== DATA uploadFile {}", (gc==null?"null":gc.userByToken)); LOGGER.info("== DATA uploadFile {}", (gc == null ? "null" : gc.userByToken));
logger.info("==================================================="); LOGGER.info("===================================================");
//public NodeSmall uploadFile(final FormDataMultiPart form) { //public NodeSmall uploadFile(final FormDataMultiPart form) {
logger.info("Upload file: "); LOGGER.info("Upload file: ");
String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++; String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++;
try { try {
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
saveFile(fileInputStream, filePath); saveFile(fileInputStream, filePath);
return Response.ok("Data uploaded successfully !!").build(); return Response.ok("Data uploaded successfully !!").build();
//return null; //return null;
} }
@GET @GET
@Path("{id}") @Path("{id}")
@PermitTokenInURI @PermitTokenInURI
@RolesAllowed("USER") @RolesAllowed("USER")
@Produces(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response retriveDataId(@Context SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) String token, @HeaderParam("Range") String range, @PathParam("id") Long id) throws Exception { public Response retriveDataId(@Context SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) String token, @HeaderParam("Range") String range, @PathParam("id") Long id) throws Exception {
GenericContext gc = (GenericContext) sc.getUserPrincipal(); GenericContext gc = (GenericContext) sc.getUserPrincipal();
//logger.info("==================================================="); //logger.info("===================================================");
logger.info("== DATA retriveDataId ? id={} user={}", id, (gc==null?"null":gc.userByToken)); LOGGER.info("== DATA retriveDataId ? id={} user={}", id, (gc == null ? "null" : gc.userByToken));
//logger.info("==================================================="); //logger.info("===================================================");
Data value = getSmall(id); Data value = getSmall(id);
if (value == null) { if (value == null) {
Response.status(404). Response.status(404).entity("media NOT FOUND: " + id).type("text/plain").build();
entity("media NOT FOUND: " + id). }
type("text/plain"). return buildStream(ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data", range, value.mimeType);
build(); }
}
return buildStream(ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data", range, value.mimeType); @GET
} @Path("thumbnail/{id}")
@RolesAllowed("USER")
@GET @PermitTokenInURI
@Path("thumbnail/{id}") @Produces(MediaType.APPLICATION_OCTET_STREAM)
@RolesAllowed("USER") //@CacheMaxAge(time = 10, unit = TimeUnit.DAYS)
@PermitTokenInURI public Response retriveDataThumbnailId(@Context SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) String token, @HeaderParam("Range") String range, @PathParam("id") Long id)
@Produces(MediaType.APPLICATION_OCTET_STREAM) throws Exception {
//@CacheMaxAge(time = 10, unit = TimeUnit.DAYS) //GenericContext gc = (GenericContext) sc.getUserPrincipal();
public Response retriveDataThumbnailId(@Context SecurityContext sc, //logger.info("===================================================");
@QueryParam(HttpHeaders.AUTHORIZATION) String token, //logger.info("== DATA retriveDataThumbnailId ? {}", (gc==null?"null":gc.user));
@HeaderParam("Range") String range, //logger.info("===================================================");
@PathParam("id") Long id) throws Exception { Data value = getSmall(id);
//GenericContext gc = (GenericContext) sc.getUserPrincipal(); if (value == null) {
//logger.info("==================================================="); return Response.status(404).entity("media NOT FOUND: " + id).type("text/plain").build();
//logger.info("== DATA retriveDataThumbnailId ? {}", (gc==null?"null":gc.user)); }
//logger.info("==================================================="); String filePathName = ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data";
Data value = getSmall(id); File inputFile = new File(filePathName);
if (value == null) { if (!inputFile.exists()) {
return Response.status(404). return Response.status(404).entity("{\"error\":\"media Does not exist: " + id + "\"}").type("application/json").build();
entity("media NOT FOUND: " + id). }
type("text/plain"). if (value.mimeType.contentEquals("image/jpeg") || value.mimeType.contentEquals("image/png")
build(); // || value.mimeType.contentEquals("image/webp")
} ) {
String filePathName = ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data"; // reads input image
File inputFile = new File(filePathName); BufferedImage inputImage = ImageIO.read(inputFile);
if (!inputFile.exists()) { int scaledWidth = 250;
return Response.status(404). int scaledHeight = (int) ((float) inputImage.getHeight() / (float) inputImage.getWidth() * (float) scaledWidth);
entity("{\"error\":\"media Does not exist: " + id + "\"}"). // creates output image
type("application/json"). BufferedImage outputImage = new BufferedImage(scaledWidth, scaledHeight, inputImage.getType());
build();
} // scales the input image to the output image
if ( value.mimeType.contentEquals("image/jpeg") Graphics2D g2d = outputImage.createGraphics();
|| value.mimeType.contentEquals("image/png") g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null);
// || value.mimeType.contentEquals("image/webp") g2d.dispose();
) { // create the output stream:
// reads input image ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedImage inputImage = ImageIO.read(inputFile); try {
int scaledWidth = 250; // TODO: check how to remove buffer file !!! here, it is not needed at all...
int scaledHeight = (int)((float)inputImage.getHeight() / (float)inputImage.getWidth() * (float) scaledWidth); ImageIO.write(outputImage, "JPG", baos);
// creates output image
BufferedImage outputImage = new BufferedImage(scaledWidth,
scaledHeight, inputImage.getType());
// scales the input image to the output image
Graphics2D g2d = outputImage.createGraphics();
g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null);
g2d.dispose();
// create the output stream:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// TODO: check how to remove buffer file !!! here, it is not needed at all...
ImageIO.write( outputImage, "JPG", baos);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
return Response.status(500). return Response.status(500).entity("Internal Error: resize fail: " + e.getMessage()).type("text/plain").build();
entity("Internal Error: resize fail: " + e.getMessage()).
type("text/plain").
build();
} }
byte[] imageData = baos.toByteArray(); byte[] imageData = baos.toByteArray();
//Response.ok(new ByteArrayInputStream(imageData)).build(); //Response.ok(new ByteArrayInputStream(imageData)).build();
Response.ResponseBuilder out = Response.ok(imageData) Response.ResponseBuilder out = Response.ok(imageData).header(HttpHeaders.CONTENT_LENGTH, imageData.length);
.header(HttpHeaders.CONTENT_LENGTH, imageData.length); out.type("image/jpeg");
out.type("image/jpeg"); // TODO: move this in a decorator !!!
// TODO: move this in a decorator !!! CacheControl cc = new CacheControl();
CacheControl cc = new CacheControl(); cc.setMaxAge(3600);
cc.setMaxAge(3600); cc.setNoCache(false);
cc.setNoCache(false); out.cacheControl(cc);
out.cacheControl(cc); return out.build();
return out.build(); }
} return buildStream(filePathName, range, value.mimeType);
return buildStream(filePathName, range, value.mimeType); }
}
//@Secured //@Secured
@GET @GET
@Path("{id}/{name}") @Path("{id}/{name}")
@PermitTokenInURI @PermitTokenInURI
@RolesAllowed("USER") @RolesAllowed("USER")
@Produces(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response retriveDataFull(@Context SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) String token, @HeaderParam("Range") String range, @PathParam("id") Long id, @PathParam("name") String name) throws Exception { public Response retriveDataFull(@Context SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) String token, @HeaderParam("Range") String range, @PathParam("id") Long id,
GenericContext gc = (GenericContext) sc.getUserPrincipal(); @PathParam("name") String name) throws Exception {
//logger.info("==================================================="); GenericContext gc = (GenericContext) sc.getUserPrincipal();
logger.info("== DATA retriveDataFull ? id={} user={}", id, (gc==null?"null":gc.userByToken)); //logger.info("===================================================");
//logger.info("==================================================="); LOGGER.info("== DATA retriveDataFull ? id={} user={}", id, (gc == null ? "null" : gc.userByToken));
Data value = getSmall(id); //logger.info("===================================================");
if (value == null) { Data value = getSmall(id);
Response.status(404). if (value == null) {
entity("media NOT FOUND: " + id). Response.status(404).entity("media NOT FOUND: " + id).type("text/plain").build();
type("text/plain"). }
build(); return buildStream(ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data", range, value.mimeType);
} }
return buildStream(ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data", range, value.mimeType);
} /**
* Adapted from http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5/12829541#12829541
/** *
* Adapted from http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5/12829541#12829541 * @param range range header
* * @return Streaming output
* @param range range header * @throws Exception IOException if an error occurs in streaming.
* @return Streaming output */
* @throws Exception IOException if an error occurs in streaming. private Response buildStream(final String filename, final String range, String mimeType) throws Exception {
*/ File file = new File(filename);
private Response buildStream(final String filename, final String range, String mimeType) throws Exception { //logger.info("request range : {}", range);
File file = new File(filename); // range not requested : Firefox does not send range headers
//logger.info("request range : {}", range); if (range == null) {
// range not requested : Firefox does not send range headers final StreamingOutput output = new StreamingOutput() {
if (range == null) { @Override
final StreamingOutput output = new StreamingOutput() { public void write(OutputStream out) {
@Override try (FileInputStream in = new FileInputStream(file)) {
public void write(OutputStream out) { byte[] buf = new byte[1024 * 1024];
try (FileInputStream in = new FileInputStream(file)) { int len;
byte[] buf = new byte[1024 * 1024]; while ((len = in.read(buf)) != -1) {
int len; try {
while ((len = in.read(buf)) != -1) { out.write(buf, 0, len);
try { out.flush();
out.write(buf, 0, len); //logger.info("---- wrote {} bytes file ----", len);
out.flush(); } catch (IOException ex) {
//logger.info("---- wrote {} bytes file ----", len); LOGGER.info("remote close connection");
} catch (IOException ex) { break;
logger.info("remote close connection"); }
break; }
} } catch (IOException ex) {
} throw new InternalServerErrorException(ex);
} catch (IOException ex) { }
throw new InternalServerErrorException(ex); }
} };
} Response.ResponseBuilder out = Response.ok(output).header(HttpHeaders.CONTENT_LENGTH, file.length());
}; if (mimeType != null) {
Response.ResponseBuilder out = Response.ok(output) out.type(mimeType);
.header(HttpHeaders.CONTENT_LENGTH, file.length()); }
if (mimeType != null) { return out.build();
out.type(mimeType);
} }
return out.build();
String[] ranges = range.split("=")[1].split("-");
} final long from = Long.parseLong(ranges[0]);
String[] ranges = range.split("=")[1].split("-"); //logger.info("request range : {}", ranges.length);
final long from = Long.parseLong(ranges[0]); //Chunk media if the range upper bound is unspecified. Chrome, Opera sends "bytes=0-"
long to = CHUNK_SIZE + from;
//logger.info("request range : {}", ranges.length); if (ranges.length == 1) {
//Chunk media if the range upper bound is unspecified. Chrome, Opera sends "bytes=0-" to = file.length() - 1;
long to = CHUNK_SIZE + from; } else {
if (ranges.length == 1) { if (to >= file.length()) {
to = file.length() - 1; to = (long) (file.length() - 1);
} else { }
if (to >= file.length()) { }
to = (long) (file.length() - 1); final String responseRange = String.format("bytes %d-%d/%d", from, to, file.length());
} //logger.info("responseRange: {}", responseRange);
} final RandomAccessFile raf = new RandomAccessFile(file, "r");
final String responseRange = String.format("bytes %d-%d/%d", from, to, file.length()); raf.seek(from);
//logger.info("responseRange: {}", responseRange);
final RandomAccessFile raf = new RandomAccessFile(file, "r"); final long len = to - from + 1;
raf.seek(from); final MediaStreamer streamer = new MediaStreamer(len, raf);
Response.ResponseBuilder out = Response.ok(streamer).status(Response.Status.PARTIAL_CONTENT).header("Accept-Ranges", "bytes").header("Content-Range", responseRange)
final long len = to - from + 1; .header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth()).header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified()));
final MediaStreamer streamer = new MediaStreamer(len, raf); if (mimeType != null) {
Response.ResponseBuilder out = Response.ok(streamer) out.type(mimeType);
.status(Response.Status.PARTIAL_CONTENT) }
.header("Accept-Ranges", "bytes") return out.build();
.header("Content-Range", responseRange) }
.header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth())
.header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified()));
if (mimeType != null) {
out.type(mimeType);
}
return out.build();
}
public static void undelete(Long id) throws Exception { public static void undelete(Long id) throws Exception {
SqlWrapper.unsetDelete(Data.class, id); SqlWrapper.unsetDelete(Data.class, id);
} }
} }

View File

@ -3,100 +3,110 @@ package org.kar.archidata.api;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import jakarta.ws.rs.*; import org.kar.archidata.annotation.security.PermitAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.NotSupportedException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.CacheControl; import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder; import jakarta.ws.rs.core.Response.ResponseBuilder;
import org.kar.archidata.annotation.security.PermitAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FrontGeneric { public class FrontGeneric {
static final Logger logger = LoggerFactory.getLogger(FrontGeneric.class); private static final Logger LOGGER = LoggerFactory.getLogger(FrontGeneric.class);
protected String baseFrontFolder = "/data/front"; protected String baseFrontFolder = "/data/front";
private String getExtension(String filename) { private String getExtension(String filename) {
if (filename.contains(".")) { if (filename.contains(".")) {
return filename.substring(filename.lastIndexOf(".") + 1); return filename.substring(filename.lastIndexOf(".") + 1);
} }
return ""; return "";
}
private Response retrive(String fileName) throws Exception {
String filePathName = baseFrontFolder + File.separator + fileName;
String extention = getExtension(filePathName);
String mineType = null;
LOGGER.debug("try retrive : '{}' '{}'", filePathName, extention);
if (extention.length() != 0 && extention.length() <= 5) {
if (extention.equalsIgnoreCase("jpg") || extention.equalsIgnoreCase("jpeg")) {
mineType = "image/jpeg";
} else if (extention.equalsIgnoreCase("gif")) {
mineType = "image/gif";
} else if (extention.equalsIgnoreCase("png")) {
mineType = "image/png";
} else if (extention.equalsIgnoreCase("svg")) {
mineType = "image/svg+xml";
} else if (extention.equalsIgnoreCase("webp")) {
mineType = "image/webp";
} else if (extention.equalsIgnoreCase("js")) {
mineType = "application/javascript";
} else if (extention.equalsIgnoreCase("json")) {
mineType = "application/json";
} else if (extention.equalsIgnoreCase("ico")) {
mineType = "image/x-icon";
} else if (extention.equalsIgnoreCase("html")) {
mineType = "text/html";
} else if (extention.equalsIgnoreCase("css")) {
mineType = "text/css";
} else if (extention.equalsIgnoreCase("mka")) {
mineType = "audio/x-matroska";
} else if (extention.equalsIgnoreCase("mkv")) {
mineType = "video/x-matroska";
} else if (extention.equalsIgnoreCase("webm")) {
mineType = "video/webm";
} else {
throw new NotSupportedException("Not supported model: '" + fileName + "'");
}
} else {
mineType = "text/html";
filePathName = baseFrontFolder + File.separator + "index.html";
}
LOGGER.debug(" ==> '[}'", filePathName);
// reads input image
File download = new File(filePathName);
if (!download.exists()) {
throw new NotFoundException("Not Found: '" + fileName + "' extension='" + extention + "'");
}
ResponseBuilder response = Response.ok((Object) download);
// use this if I want to download the file:
//response.header("Content-Disposition", "attachment; filename=" + fileName);
CacheControl cc = new CacheControl();
cc.setMaxAge(60);
cc.setNoCache(false);
response.cacheControl(cc);
response.type(mineType);
return response.build();
}
@GET
@PermitAll()
//@Produces(MediaType.APPLICATION_OCTET_STREAM)
//@CacheMaxAge(time = 1, unit = TimeUnit.DAYS)
public Response retrive0() throws Exception {
return retrive("index.html");
}
@GET
@Path("{any: .*}")
@PermitAll()
//@Produces(MediaType.APPLICATION_OCTET_STREAM)
//@CacheMaxAge(time = 10, unit = TimeUnit.DAYS)
public Response retrive1(@PathParam("any") List<PathSegment> segments) throws Exception {
String filename = "";
for (PathSegment elem : segments) {
if (!filename.isEmpty()) {
filename += File.separator;
}
filename += elem.getPath();
}
return retrive(filename);
} }
private Response retrive(String fileName) throws Exception {
String filePathName = baseFrontFolder + File.separator + fileName;
String extention = getExtension(filePathName);
String mineType = null;
logger.debug("try retrive : '{}' '{}'", filePathName, extention);
if (extention.length() !=0 && extention.length() <= 5) {
if (extention.equalsIgnoreCase("jpg") || extention.equalsIgnoreCase("jpeg")) {
mineType = "image/jpeg";
} else if (extention.equalsIgnoreCase("gif")) {
mineType = "image/gif";
} else if (extention.equalsIgnoreCase("png")) {
mineType = "image/png";
} else if (extention.equalsIgnoreCase("svg")) {
mineType = "image/svg+xml";
} else if (extention.equalsIgnoreCase("webp")) {
mineType = "image/webp";
} else if (extention.equalsIgnoreCase("js")) {
mineType = "application/javascript";
} else if (extention.equalsIgnoreCase("json")) {
mineType = "application/json";
} else if (extention.equalsIgnoreCase("ico")) {
mineType = "image/x-icon";
} else if (extention.equalsIgnoreCase("html")) {
mineType = "text/html";
} else if (extention.equalsIgnoreCase("css")) {
mineType = "text/css";
} else {
throw new NotSupportedException("Not supported model: '" + fileName + "'");
}
} else {
mineType = "text/html";
filePathName = baseFrontFolder + File.separator + "index.html";
}
logger.debug(" ==> '[}'", filePathName);
// reads input image
File download = new File(filePathName);
if (!download.exists()) {
throw new NotFoundException("Not Found: '" + fileName + "' extension='" + extention + "'");
}
ResponseBuilder response = Response.ok((Object)download);
// use this if I want to download the file:
//response.header("Content-Disposition", "attachment; filename=" + fileName);
CacheControl cc = new CacheControl();
cc.setMaxAge(60);
cc.setNoCache(false);
response.cacheControl(cc);
response.type(mineType);
return response.build();
}
@GET
@PermitAll()
//@Produces(MediaType.APPLICATION_OCTET_STREAM)
//@CacheMaxAge(time = 1, unit = TimeUnit.DAYS)
public Response retrive0() throws Exception {
return retrive("index.html");
}
@GET
@Path("{any: .*}")
@PermitAll()
//@Produces(MediaType.APPLICATION_OCTET_STREAM)
//@CacheMaxAge(time = 10, unit = TimeUnit.DAYS)
public Response retrive1(@PathParam("any") List<PathSegment> segments) throws Exception {
String filename = "";
for (PathSegment elem: segments) {
if (!filename.isEmpty()) {
filename += File.separator;
}
filename += elem.getPath();
}
return retrive(filename);
}
} }

View File

@ -1,8 +1,5 @@
package org.kar.archidata.api; package org.kar.archidata.api;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.StreamingOutput;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
@ -10,51 +7,55 @@ import java.io.RandomAccessFile;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.StreamingOutput;
public class MediaStreamer implements StreamingOutput { public class MediaStreamer implements StreamingOutput {
static final Logger logger = LoggerFactory.getLogger(MediaStreamer.class); private static final Logger LOGGER = LoggerFactory.getLogger(MediaStreamer.class);
private final int CHUNK_SIZE = 1024 * 1024; // 1MB chunks private final int CHUNK_SIZE = 1024 * 1024; // 1MB chunks
final byte[] buf = new byte[CHUNK_SIZE]; final byte[] buf = new byte[CHUNK_SIZE];
private long length; private long length;
private RandomAccessFile raf; private RandomAccessFile raf;
public MediaStreamer(long length, RandomAccessFile raf) throws IOException { public MediaStreamer(long length, RandomAccessFile raf) throws IOException {
//logger.info("request stream of {} data", length / 1024); //logger.info("request stream of {} data", length / 1024);
if (length<0) { if (length < 0) {
throw new IOException("Wrong size of the file to stream: " + length); throw new IOException("Wrong size of the file to stream: " + length);
} }
this.length = length; this.length = length;
this.raf = raf; this.raf = raf;
} }
@Override @Override
public void write(OutputStream outputStream) { public void write(OutputStream outputStream) {
try { try {
while (length != 0) { while (length != 0) {
int read = raf.read(buf, 0, buf.length > length ? (int) length : buf.length); int read = raf.read(buf, 0, buf.length > length ? (int) length : buf.length);
try { try {
outputStream.write(buf, 0, read); outputStream.write(buf, 0, read);
} catch (IOException ex) { } catch (IOException ex) {
logger.info("remote close connection"); LOGGER.info("remote close connection");
break; break;
} }
length -= read; length -= read;
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new InternalServerErrorException(ex); throw new InternalServerErrorException(ex);
} catch (WebApplicationException ex) { } catch (WebApplicationException ex) {
throw new InternalServerErrorException(ex); throw new InternalServerErrorException(ex);
} finally { } finally {
try { try {
raf.close(); raf.close();
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); ex.printStackTrace();
throw new InternalServerErrorException(ex); throw new InternalServerErrorException(ex);
} }
} }
} }
public long getLenth() { public long getLenth() {
return length; return length;
} }
} }

View File

@ -1,29 +1,26 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import jakarta.ws.rs.core.MediaType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.ExceptionMapper;
public class ExceptionCatcher implements ExceptionMapper<Exception> {
public class ExceptionCatcher private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatcher.class);
implements ExceptionMapper<Exception> {
final Logger logger = LoggerFactory.getLogger(ExceptionCatcher.class); @Override
@Override public Response toResponse(Exception exception) {
public Response toResponse(Exception exception) { LOGGER.warn("Catch exception (not managed...):");
logger.warn("Catch exception (not managed...):"); RestErrorResponse ret = build(exception);
RestErrorResponse ret = build(exception); LOGGER.error("Error UUID={}", ret.uuid);
logger.error("Error UUID={}", ret.uuid); exception.printStackTrace();
exception.printStackTrace(); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ret).type(MediaType.APPLICATION_JSON).build();
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) }
.entity(ret)
.type(MediaType.APPLICATION_JSON) private RestErrorResponse build(Exception exception) {
.build(); return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Catch Unknown Exception", exception.getMessage());
} }
private RestErrorResponse build(Exception exception) {
return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Catch Unknown Exception", exception.getMessage());
}
} }

View File

@ -1,29 +1,25 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.ExceptionMapper;
import org.slf4j.Logger; public class FailException404API implements ExceptionMapper<ClientErrorException> {
import org.slf4j.LoggerFactory; private static final Logger LOGGER = LoggerFactory.getLogger(FailException404API.class);
@Override
public class FailException404API public Response toResponse(ClientErrorException exception) {
implements ExceptionMapper<ClientErrorException> { RestErrorResponse ret = build(exception);
final Logger logger = LoggerFactory.getLogger(FailException404API.class); LOGGER.error("Error UUID={}", ret.uuid);
@Override return Response.status(exception.getResponse().getStatusInfo().toEnum()).entity(ret).type(MediaType.APPLICATION_JSON).build();
public Response toResponse(ClientErrorException exception) { }
RestErrorResponse ret = build(exception);
logger.error("Error UUID={}", ret.uuid); private RestErrorResponse build(ClientErrorException exception) {
return Response.status(exception.getResponse().getStatusInfo().toEnum()) return new RestErrorResponse(exception.getResponse().getStatusInfo().toEnum(), "Catch system exception", exception.getMessage());
.entity(ret) }
.type(MediaType.APPLICATION_JSON)
.build();
}
private RestErrorResponse build(ClientErrorException exception) {
return new RestErrorResponse(exception.getResponse().getStatusInfo().toEnum(), "Catch system exception" , exception.getMessage());
}
} }

View File

@ -1,31 +1,27 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import org.kar.archidata.exception.FailException; import org.kar.archidata.exception.FailException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class FailExceptionCatcher public class FailExceptionCatcher implements ExceptionMapper<FailException> {
implements ExceptionMapper<FailException> { private static final Logger LOGGER = LoggerFactory.getLogger(FailExceptionCatcher.class);
final Logger logger = LoggerFactory.getLogger(FailExceptionCatcher.class);
@Override @Override
public Response toResponse(FailException exception) { public Response toResponse(FailException exception) {
RestErrorResponse ret = build(exception); RestErrorResponse ret = build(exception);
logger.error("Error UUID={}", ret.uuid); LOGGER.error("Error UUID={}", ret.uuid);
// Not display backtrace ==> this may be a normal case ... // Not display backtrace ==> this may be a normal case ...
//exception.printStackTrace(); //exception.printStackTrace();
return Response.status(exception.status) return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
.entity(ret) }
.type(MediaType.APPLICATION_JSON)
.build(); private RestErrorResponse build(FailException exception) {
} return new RestErrorResponse(exception.status, "Request Fail", exception.getMessage());
}
private RestErrorResponse build(FailException exception) {
return new RestErrorResponse(exception.status, "Request Fail", exception.getMessage());
}
} }

View File

@ -1,30 +1,26 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import org.kar.archidata.exception.InputException; import org.kar.archidata.exception.InputException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class InputExceptionCatcher public class InputExceptionCatcher implements ExceptionMapper<InputException> {
implements ExceptionMapper<InputException> { private static final Logger LOGGER = LoggerFactory.getLogger(InputExceptionCatcher.class);
final Logger logger = LoggerFactory.getLogger(InputExceptionCatcher.class);
@Override @Override
public Response toResponse(InputException exception) { public Response toResponse(InputException exception) {
RestErrorResponse ret = build(exception); RestErrorResponse ret = build(exception);
logger.error("Error UUID={}", ret.uuid); LOGGER.error("Error UUID={}", ret.uuid);
exception.printStackTrace(); exception.printStackTrace();
return Response.status(exception.status) return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
.entity(ret) }
.type(MediaType.APPLICATION_JSON)
.build(); private RestErrorResponse build(InputException exception) {
} return new RestErrorResponse(exception.status, "Error on input='" + exception.missingVariable + "'", exception.getMessage());
}
private RestErrorResponse build(InputException exception) {
return new RestErrorResponse(exception.status, "Error on input='" + exception.missingVariable + "'" , exception.getMessage());
}
} }

View File

@ -7,7 +7,7 @@ import jakarta.ws.rs.core.Response;
public class RestErrorResponse { public class RestErrorResponse {
public UUID uuid = UUID.randomUUID(); public UUID uuid = UUID.randomUUID();
public String time; public String time;
public String error; public String error;
public String message; public String message;
final public int status; final public int status;
@ -20,6 +20,7 @@ public class RestErrorResponse {
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
} }
public RestErrorResponse(Response.Status status, String error, String message) { public RestErrorResponse(Response.Status status, String error, String message) {
this.time = Instant.now().toString(); this.time = Instant.now().toString();
this.error = error; this.error = error;
@ -27,6 +28,7 @@ public class RestErrorResponse {
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
} }
public RestErrorResponse(Response.Status status) { public RestErrorResponse(Response.Status status) {
this.time = Instant.now().toString(); this.time = Instant.now().toString();
this.status = status.getStatusCode(); this.status = status.getStatusCode();

View File

@ -1,30 +1,26 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import org.kar.archidata.exception.SystemException; import org.kar.archidata.exception.SystemException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class SystemExceptionCatcher public class SystemExceptionCatcher implements ExceptionMapper<SystemException> {
implements ExceptionMapper<SystemException> { private static final Logger LOGGER = LoggerFactory.getLogger(SystemExceptionCatcher.class);
final Logger logger = LoggerFactory.getLogger(SystemExceptionCatcher.class);
@Override @Override
public Response toResponse(SystemException exception) { public Response toResponse(SystemException exception) {
RestErrorResponse ret = build(exception); RestErrorResponse ret = build(exception);
logger.error("Error UUID={}", ret.uuid); LOGGER.error("Error UUID={}", ret.uuid);
exception.printStackTrace(); exception.printStackTrace();
return Response.status(exception.status) return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
.entity(ret) }
.type(MediaType.APPLICATION_JSON)
.build(); private RestErrorResponse build(SystemException exception) {
} return new RestErrorResponse(exception.status, "System error", exception.getMessage());
}
private RestErrorResponse build(SystemException exception) {
return new RestErrorResponse(exception.status, "System error", exception.getMessage());
}
} }

View File

@ -6,87 +6,82 @@ import org.slf4j.LoggerFactory;
public class DBConfig { public class DBConfig {
static final Logger LOGGER = LoggerFactory.getLogger(SqlWrapper.class); static final Logger LOGGER = LoggerFactory.getLogger(SqlWrapper.class);
private final String type; private final String type;
private final String hostname; private final String hostname;
private final int port; private final int port;
private final String login; private final String login;
private final String password; private final String password;
private final String dbName; private final String dbName;
private final boolean keepConnected; private final boolean keepConnected;
public DBConfig(String type, String hostname, Integer port, String login, String password, String dbName, boolean keepConnected) { public DBConfig(String type, String hostname, Integer port, String login, String password, String dbName, boolean keepConnected) {
if (type == null) { if (type == null) {
this.type = "mysql"; this.type = "mysql";
} else { } else {
this.type = type; this.type = type;
} }
if (hostname == null) { if (hostname == null) {
this.hostname = "localhost"; this.hostname = "localhost";
} else { } else {
this.hostname = hostname; this.hostname = hostname;
} }
if (port == null) { if (port == null) {
this.port = 3306; this.port = 3306;
} else { } else {
this.port = port; this.port = port;
} }
this.login = login; this.login = login;
this.password = password; this.password = password;
this.dbName = dbName; this.dbName = dbName;
this.keepConnected = keepConnected; this.keepConnected = keepConnected;
} }
@Override @Override
public String toString() { public String toString() {
return "DBConfig{" + return "DBConfig{type='" + type + '\'' + ", hostname='" + hostname + '\'' + ", port=" + port + ", login='" + login + '\'' + ", password='" + password + '\'' + ", dbName='" + dbName + "' }";
"type='" + type + '\'' + }
", hostname='" + hostname + '\'' +
", port=" + port + public String getHostname() {
", login='" + login + '\'' + return hostname;
", password='" + password + '\'' + }
", dbName='" + dbName + '\'' +
'}'; public int getPort() {
} return port;
}
public String getHostname() {
return hostname; public String getLogin() {
} return login;
}
public int getPort() {
return port; public String getPassword() {
} return password;
}
public String getLogin() {
return login; public String getDbName() {
} return dbName;
}
public String getPassword() {
return password; public boolean getKeepConnected() {
} return keepConnected;
}
public String getDbName() {
return dbName; public String getUrl() {
} return getUrl(false);
public boolean getKeepConnected() { }
return keepConnected;
} public String getUrl(boolean isRoot) {
if (type.equals("sqlite")) {
public String getUrl() { if (isRoot == true) {
return getUrl(false); LOGGER.error("Can not manage root connection on SQLite...");
} }
public String getUrl(boolean isRoot) { if (this.hostname.equals("memory")) {
if (type.equals("sqlite")) { return "jdbc:sqlite::memory:";
if (isRoot == true) { }
LOGGER.error("Can not manage root connection on SQLite..."); return "jdbc:sqlite:" + this.hostname + ".db";
} }
if (this.hostname.equals("memory")) { if (isRoot) {
return "jdbc:sqlite::memory:"; return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
} }
return "jdbc:sqlite:" + this.hostname + ".db";
}
if (isRoot) {
return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
}
return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/" + this.dbName + "?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"; return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/" + this.dbName + "?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
} }
} }

View File

@ -2,7 +2,9 @@ package org.kar.archidata.db;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.sql.*; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -11,69 +13,70 @@ import org.slf4j.LoggerFactory;
public class DBEntry implements Closeable { public class DBEntry implements Closeable {
final static Logger LOGGER = LoggerFactory.getLogger(DBEntry.class); final static Logger LOGGER = LoggerFactory.getLogger(DBEntry.class);
public DBConfig config; public DBConfig config;
public Connection connection; public Connection connection;
private static List<DBEntry> stored = new ArrayList<>(); private static List<DBEntry> stored = new ArrayList<>();
private DBEntry(DBConfig config, boolean root) throws IOException { private DBEntry(DBConfig config, boolean root) throws IOException {
this.config = config; this.config = config;
if (root) { if (root) {
connectRoot(); connectRoot();
} else { } else {
connect(); connect();
} }
} }
public static DBEntry createInterface(DBConfig config) throws IOException { public static DBEntry createInterface(DBConfig config) throws IOException {
return createInterface(config, false); return createInterface(config, false);
} }
public static DBEntry createInterface(DBConfig config, boolean root) throws IOException {
if (config.getKeepConnected()) { public static DBEntry createInterface(DBConfig config, boolean root) throws IOException {
for (DBEntry elem : stored) { if (config.getKeepConnected()) {
if (elem == null) { for (DBEntry elem : stored) {
continue; if (elem == null) {
} continue;
if (elem.config.getUrl().equals(config.getUrl())) { }
return elem; if (elem.config.getUrl().equals(config.getUrl())) {
} return elem;
} }
DBEntry tmp = new DBEntry(config, root); }
stored.add(tmp); DBEntry tmp = new DBEntry(config, root);
return tmp; stored.add(tmp);
} else { return tmp;
return new DBEntry(config, root); } else {
} return new DBEntry(config, root);
} }
}
public void connectRoot() throws IOException {
try { public void connectRoot() throws IOException {
connection = DriverManager.getConnection(config.getUrl(true), config.getLogin(), config.getPassword()); try {
} catch (SQLException ex) { connection = DriverManager.getConnection(config.getUrl(true), config.getLogin(), config.getPassword());
throw new IOException("Connection db fail: " + ex.getMessage()); } catch (SQLException ex) {
} throw new IOException("Connection db fail: " + ex.getMessage());
}
}
}
public void connect() throws IOException {
try { public void connect() throws IOException {
connection = DriverManager.getConnection(config.getUrl(), config.getLogin(), config.getPassword()); try {
} catch (SQLException ex) { connection = DriverManager.getConnection(config.getUrl(), config.getLogin(), config.getPassword());
throw new IOException("Connection db fail: " + ex.getMessage()); } catch (SQLException ex) {
} throw new IOException("Connection db fail: " + ex.getMessage());
}
}
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (config.getKeepConnected()) { if (config.getKeepConnected()) {
return; return;
} }
try { try {
//connection.commit(); //connection.commit();
connection.close(); connection.close();
} catch (SQLException ex) { } catch (SQLException ex) {
throw new IOException("Dis-connection db fail: " + ex.getMessage()); throw new IOException("Dis-connection db fail: " + ex.getMessage());
} }
} }
} }

View File

@ -5,10 +5,12 @@ import jakarta.ws.rs.core.Response;
public class FailException extends Exception { public class FailException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public final Response.Status status; public final Response.Status status;
public FailException(Response.Status status, String message) { public FailException(Response.Status status, String message) {
super(message); super(message);
this.status = status; this.status = status;
} }
public FailException(String message) { public FailException(String message) {
super(message); super(message);
this.status = Response.Status.BAD_REQUEST; this.status = Response.Status.BAD_REQUEST;

View File

@ -6,11 +6,13 @@ public class InputException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public final String missingVariable; public final String missingVariable;
public final Response.Status status; public final Response.Status status;
public InputException(Response.Status status, String variable, String message) { public InputException(Response.Status status, String variable, String message) {
super(message); super(message);
this.missingVariable = variable; this.missingVariable = variable;
this.status = status; this.status = status;
} }
public InputException(String variable, String message) { public InputException(String variable, String message) {
super(message); super(message);
this.missingVariable = variable; this.missingVariable = variable;

View File

@ -4,6 +4,7 @@ import jakarta.ws.rs.core.Response;
public class NotFoundException extends FailException { public class NotFoundException extends FailException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public NotFoundException(String message) { public NotFoundException(String message) {
super(Response.Status.NOT_FOUND, message); super(Response.Status.NOT_FOUND, message);
} }

View File

@ -4,12 +4,12 @@ import java.util.UUID;
public class RESTErrorResponseExeption extends Exception { public class RESTErrorResponseExeption extends Exception {
public UUID uuid; public UUID uuid;
public String time; public String time;
public String error; public String error;
public String message; public String message;
public int status; public int status;
public String statusMessage; public String statusMessage;
public RESTErrorResponseExeption() { public RESTErrorResponseExeption() {
super(); super();
this.uuid = null; this.uuid = null;
@ -19,8 +19,8 @@ public class RESTErrorResponseExeption extends Exception {
this.status = 0; this.status = 0;
this.statusMessage = null; this.statusMessage = null;
} }
public RESTErrorResponseExeption(UUID uuid, String time, String error, String message, int status,
String statusMessage) { public RESTErrorResponseExeption(UUID uuid, String time, String error, String message, int status, String statusMessage) {
super(); super();
this.uuid = uuid; this.uuid = uuid;
this.time = time; this.time = time;
@ -29,13 +29,10 @@ public class RESTErrorResponseExeption extends Exception {
this.status = status; this.status = status;
this.statusMessage = statusMessage; this.statusMessage = statusMessage;
} }
@Override @Override
public String toString() { public String toString() {
return "RESTErrorResponseExeption [uuid=" + uuid + ", time=" + time + ", error=" + error + ", message=" return "RESTErrorResponseExeption [uuid=" + uuid + ", time=" + time + ", error=" + error + ", message=" + message + ", status=" + status + ", statusMessage=" + statusMessage + "]";
+ message + ", status=" + status + ", statusMessage=" + statusMessage + "]";
} }
} }

View File

@ -5,10 +5,12 @@ import jakarta.ws.rs.core.Response;
public class SystemException extends Exception { public class SystemException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public final Response.Status status; public final Response.Status status;
public SystemException(Response.Status status, String message) { public SystemException(Response.Status status, String message) {
super(message); super(message);
this.status = status; this.status = status;
} }
public SystemException(String message) { public SystemException(String message) {
super(message); super(message);
this.status = Response.Status.INTERNAL_SERVER_ERROR; this.status = Response.Status.INTERNAL_SERVER_ERROR;

View File

@ -4,6 +4,7 @@ import jakarta.ws.rs.core.Response;
public class UnAuthorizedException extends FailException { public class UnAuthorizedException extends FailException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public UnAuthorizedException(String message) { public UnAuthorizedException(String message) {
super(Response.Status.UNAUTHORIZED, message); super(Response.Status.UNAUTHORIZED, message);
} }

View File

@ -1,10 +1,26 @@
package org.kar.archidata.filter; package org.kar.archidata.filter;
import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey/45814178#45814178
// https://stackoverflow.com/questions/32817210/how-to-access-jersey-resource-secured-by-rolesallowed
import org.kar.archidata.annotation.security.DenyAll; import org.kar.archidata.annotation.security.DenyAll;
import org.kar.archidata.annotation.security.PermitAll; import org.kar.archidata.annotation.security.PermitAll;
import org.kar.archidata.annotation.security.PermitTokenInURI;
import org.kar.archidata.annotation.security.RolesAllowed; import org.kar.archidata.annotation.security.RolesAllowed;
import org.kar.archidata.catcher.RestErrorResponse; import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.model.UserByToken;
import org.kar.archidata.util.JWTWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nimbusds.jwt.JWTClaimsSet;
import jakarta.annotation.Priority; import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities; import jakarta.ws.rs.Priorities;
@ -18,23 +34,6 @@ import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import org.kar.archidata.annotation.security.PermitTokenInURI;
import org.kar.archidata.model.UserByToken;
import org.kar.archidata.util.JWTWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nimbusds.jwt.JWTClaimsSet;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey/45814178#45814178
// https://stackoverflow.com/questions/32817210/how-to-access-jersey-resource-secured-by-rolesallowed
//@PreMatching //@PreMatching
@Provider @Provider
@Priority(Priorities.AUTHENTICATION) @Priority(Priorities.AUTHENTICATION)
@ -43,189 +42,183 @@ public class AuthenticationFilter implements ContainerRequestFilter {
@Context @Context
private ResourceInfo resourceInfo; private ResourceInfo resourceInfo;
protected final String applicationName; protected final String applicationName;
private static final String AUTHENTICATION_SCHEME = "Yota"; private static final String AUTHENTICATION_SCHEME = "Yota";
private static final String AUTHENTICATION_TOKEN_SCHEME = "Zota"; private static final String AUTHENTICATION_TOKEN_SCHEME = "Zota";
public AuthenticationFilter(String applicationName) {
public AuthenticationFilter(String applicationName) {
super(); super();
this.applicationName = applicationName; this.applicationName = applicationName;
} }
@Override @Override
public void filter(ContainerRequestContext requestContext) throws IOException { public void filter(ContainerRequestContext requestContext) throws IOException {
/* /*
logger.debug("-----------------------------------------------------"); logger.debug("-----------------------------------------------------");
logger.debug("---- Check if have authorization ----"); logger.debug("---- Check if have authorization ----");
logger.debug("-----------------------------------------------------"); logger.debug("-----------------------------------------------------");
logger.debug(" for:{}", requestContext.getUriInfo().getPath()); logger.debug(" for:{}", requestContext.getUriInfo().getPath());
*/ */
Method method = resourceInfo.getResourceMethod(); Method method = resourceInfo.getResourceMethod();
// Access denied for all // Access denied for all
if(method.isAnnotationPresent(DenyAll.class)) { if (method.isAnnotationPresent(DenyAll.class)) {
logger.debug(" ==> deny all {}", requestContext.getUriInfo().getPath()); logger.debug(" ==> deny all {}", requestContext.getUriInfo().getPath());
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access blocked !!!").build()); requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access blocked !!!").build());
return; return;
} }
//Access allowed for all //Access allowed for all
if( method.isAnnotationPresent(PermitAll.class)) { if (method.isAnnotationPresent(PermitAll.class)) {
//logger.debug(" ==> permit all " + requestContext.getUriInfo().getPath()); //logger.debug(" ==> permit all " + requestContext.getUriInfo().getPath());
// no control ... // no control ...
return; return;
} }
// this is a security guard, all the API must define their access level: // this is a security guard, all the API must define their access level:
if(!method.isAnnotationPresent(RolesAllowed.class)) { if (!method.isAnnotationPresent(RolesAllowed.class)) {
logger.error(" ==> missing @RolesAllowed {}", requestContext.getUriInfo().getPath()); logger.error(" ==> missing @RolesAllowed {}", requestContext.getUriInfo().getPath());
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access ILLEGAL !!!").build()); requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access ILLEGAL !!!").build());
return; return;
} }
// Get the Authorization header from the request // Get the Authorization header from the request
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
//logger.debug("authorizationHeader: {}", authorizationHeader); //logger.debug("authorizationHeader: {}", authorizationHeader);
if(authorizationHeader == null && method.isAnnotationPresent(PermitTokenInURI.class)) { if (authorizationHeader == null && method.isAnnotationPresent(PermitTokenInURI.class)) {
MultivaluedMap<String, String> quaryparam = requestContext.getUriInfo().getQueryParameters(); MultivaluedMap<String, String> quaryparam = requestContext.getUriInfo().getQueryParameters();
for (Entry<String, List<String>> item: quaryparam.entrySet()) { for (Entry<String, List<String>> item : quaryparam.entrySet()) {
if (item.getKey().equals(HttpHeaders.AUTHORIZATION)) { if (item.getKey().equals(HttpHeaders.AUTHORIZATION)) {
if (!item.getValue().isEmpty()) { if (!item.getValue().isEmpty()) {
authorizationHeader = item.getValue().get(0); authorizationHeader = item.getValue().get(0);
} }
break; break;
} }
} }
} }
// logger.debug("authorizationHeader: {}", authorizationHeader); // logger.debug("authorizationHeader: {}", authorizationHeader);
boolean isApplicationToken = isApplicationTokenBasedAuthentication(authorizationHeader); boolean isApplicationToken = isApplicationTokenBasedAuthentication(authorizationHeader);
boolean isJwtToken = isTokenBasedAuthentication(authorizationHeader); boolean isJwtToken = isTokenBasedAuthentication(authorizationHeader);
// Validate the Authorization header data Model "Yota jwt.to.ken" "Zota tokenId:hash(token)" // Validate the Authorization header data Model "Yota jwt.to.ken" "Zota tokenId:hash(token)"
if (!isApplicationToken && !isJwtToken) { if (!isApplicationToken && !isJwtToken) {
logger.warn("REJECTED unauthorized: {}", requestContext.getUriInfo().getPath()); logger.warn("REJECTED unauthorized: {}", requestContext.getUriInfo().getPath());
abortWithUnauthorized(requestContext, "REJECTED unauthorized: " + requestContext.getUriInfo().getPath()); abortWithUnauthorized(requestContext, "REJECTED unauthorized: " + requestContext.getUriInfo().getPath());
return; return;
} }
UserByToken userByToken = null; UserByToken userByToken = null;
if (isJwtToken) { if (isJwtToken) {
// Extract the token from the Authorization header (Remove "Yota ") // Extract the token from the Authorization header (Remove "Yota ")
String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim(); String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim();
//logger.debug("token: {}", token); //logger.debug("token: {}", token);
try { try {
userByToken = validateJwtToken(token); userByToken = validateJwtToken(token);
} catch (Exception e) { } catch (Exception e) {
logger.error("Fail to validate token: {}", e.getMessage()); logger.error("Fail to validate token: {}", e.getMessage());
abortWithUnauthorized(requestContext, "Fail to validate token: " + e.getMessage()); abortWithUnauthorized(requestContext, "Fail to validate token: " + e.getMessage());
return; return;
} }
if (userByToken == null) { if (userByToken == null) {
logger.warn("get a NULL user ..."); logger.warn("get a NULL user ...");
abortWithUnauthorized(requestContext, "get a NULL user ..."); abortWithUnauthorized(requestContext, "get a NULL user ...");
return; return;
} }
} else { } else {
// Extract the token from the Authorization header (Remove "Zota ") // Extract the token from the Authorization header (Remove "Zota ")
String token = authorizationHeader.substring(AUTHENTICATION_TOKEN_SCHEME.length()).trim(); String token = authorizationHeader.substring(AUTHENTICATION_TOKEN_SCHEME.length()).trim();
//logger.debug("token: {}", token); //logger.debug("token: {}", token);
try { try {
userByToken = validateToken(token); userByToken = validateToken(token);
} catch (Exception e) { } catch (Exception e) {
logger.error("Fail to validate token: {}", e.getMessage()); logger.error("Fail to validate token: {}", e.getMessage());
abortWithUnauthorized(requestContext, "Fail to validate token: "+ e.getMessage()); abortWithUnauthorized(requestContext, "Fail to validate token: " + e.getMessage());
return; return;
} }
if (userByToken == null) { if (userByToken == null) {
logger.warn("get a NULL application ..."); logger.warn("get a NULL application ...");
abortWithUnauthorized(requestContext, "get a NULL application ..."); abortWithUnauthorized(requestContext, "get a NULL application ...");
return; return;
} }
} }
// create the security context model: // create the security context model:
String scheme = requestContext.getUriInfo().getRequestUri().getScheme(); String scheme = requestContext.getUriInfo().getRequestUri().getScheme();
MySecurityContext userContext = new MySecurityContext(userByToken, scheme); MySecurityContext userContext = new MySecurityContext(userByToken, scheme);
// retrieve the allowed right: // retrieve the allowed right:
RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class); RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
List<String> roles = Arrays.asList(rolesAnnotation.value()); List<String> roles = Arrays.asList(rolesAnnotation.value());
// check if the user have the right: // check if the user have the right:
boolean haveRight = false; boolean haveRight = false;
for (String role : roles) { for (String role : roles) {
if (userContext.isUserInRole(role)) { if (userContext.isUserInRole(role)) {
haveRight = true; haveRight = true;
break; break;
} }
} }
//Is user valid? //Is user valid?
if( ! haveRight) { if (!haveRight) {
logger.error("REJECTED not enought right : {} require: {}", requestContext.getUriInfo().getPath(), roles); logger.error("REJECTED not enought right : {} require: {}", requestContext.getUriInfo().getPath(), roles);
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Not enought RIGHT !!!").build()); requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Not enought RIGHT !!!").build());
return; return;
} }
requestContext.setSecurityContext(userContext); requestContext.setSecurityContext(userContext);
// logger.debug("Get local user : {} / {}", user, userByToken); // logger.debug("Get local user : {} / {}", user, userByToken);
} }
private boolean isTokenBasedAuthentication(String authorizationHeader) { private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid // Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace // It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive // The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " "); return authorizationHeader != null && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
} }
private boolean isApplicationTokenBasedAuthentication(String authorizationHeader) { private boolean isApplicationTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid // Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace // It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive // The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_TOKEN_SCHEME.toLowerCase() + " "); return authorizationHeader != null && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_TOKEN_SCHEME.toLowerCase() + " ");
} }
private void abortWithUnauthorized(ContainerRequestContext requestContext, String message) {
private void abortWithUnauthorized(ContainerRequestContext requestContext, String message) {
// Abort the filter chain with a 401 status code response
// Abort the filter chain with a 401 status code response // The WWW-Authenticate header is sent along with the response
// The WWW-Authenticate header is sent along with the response logger.warn("abortWithUnauthorized:");
logger.warn("abortWithUnauthorized:");
RestErrorResponse ret = new RestErrorResponse(Response.Status.UNAUTHORIZED, "Unauthorized", message); RestErrorResponse ret = new RestErrorResponse(Response.Status.UNAUTHORIZED, "Unauthorized", message);
logger.error("Error UUID={}", ret.uuid); logger.error("Error UUID={}", ret.uuid);
requestContext.abortWith(Response.status(ret.status) requestContext.abortWith(Response.status(ret.status).header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME + " base64(HEADER).base64(CONTENT).base64(KEY)").entity(ret)
.header(HttpHeaders.WWW_AUTHENTICATE, .type(MediaType.APPLICATION_JSON).build());
AUTHENTICATION_SCHEME + " base64(HEADER).base64(CONTENT).base64(KEY)") }
.entity(ret)
.type(MediaType.APPLICATION_JSON) protected UserByToken validateToken(String authorization) throws Exception {
.build()); logger.info("Must be Override by the application implmentation, otherwise it dose not work");
} return null;
}
protected UserByToken validateToken(String authorization) throws Exception {
logger.info("Must be Override by the application implmentation, otherwise it dose not work"); // must be override to be good implementation
return null; protected UserByToken validateJwtToken(String authorization) throws Exception {
} //logger.debug(" validate token : " + authorization);
// must be override to be good implementation JWTClaimsSet ret = JWTWrapper.validateToken(authorization, "KarAuth", null);
protected UserByToken validateJwtToken(String authorization) throws Exception { // check the token is valid !!! (signed and coherent issuer...
//logger.debug(" validate token : " + authorization); if (ret == null) {
JWTClaimsSet ret = JWTWrapper.validateToken(authorization, "KarAuth", null); logger.error("The token is not valid: '{}'", authorization);
// check the token is valid !!! (signed and coherent issuer... return null;
if (ret == null) { }
logger.error("The token is not valid: '{}'", authorization); // check userID
return null; String userUID = ret.getSubject();
} long id = Long.parseLong(userUID);
// check userID UserByToken user = new UserByToken();
String userUID = ret.getSubject(); user.id = id;
long id = Long.parseLong(userUID); user.name = (String) ret.getClaim("login");
UserByToken user = new UserByToken(); user.type = UserByToken.TYPE_USER;
user.id = id; Object rowRight = ret.getClaim("right");
user.name = (String)ret.getClaim("login"); if (rowRight != null) {
user.type = UserByToken.TYPE_USER; Map<String, Map<String, Object>> rights = (Map<String, Map<String, Object>>) ret.getClaim("right");
Object rowRight = ret.getClaim("right"); if (rights.containsKey(this.applicationName)) {
if (rowRight != null) { user.right = rights.get(this.applicationName);
Map<String, Map<String,Object>> rights = (Map<String, Map<String,Object>>) ret.getClaim("right"); } else {
if (rights.containsKey(this.applicationName)) { logger.error("Connect with no right for this application='{}' full Right='{}'", this.applicationName, rights);
user.right = rights.get(this.applicationName); }
} else { }
logger.error("Connect with no right for this application='{}' full Right='{}'", this.applicationName, rights); //logger.debug("request user: '{}' right: '{}' row='{}'", userUID, user.right, rowRight);
} return user;
} //return UserDB.getUserOrCreate(id, (String)ret.getClaim("login") );
//logger.debug("request user: '{}' right: '{}' row='{}'", userUID, user.right, rowRight); }
return user;
//return UserDB.getUserOrCreate(id, (String)ret.getClaim("login") );
}
} }

View File

@ -1,25 +1,23 @@
package org.kar.archidata.filter; package org.kar.archidata.filter;
import java.io.IOException;
import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
@Provider @Provider
public class CORSFilter implements ContainerResponseFilter { public class CORSFilter implements ContainerResponseFilter {
@Override @Override
public void filter(ContainerRequestContext request, public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException {
ContainerResponseContext response) throws IOException { //System.err.println("filter cors ..." + request.toString());
//System.err.println("filter cors ..." + request.toString());
response.getHeaders().add("Access-Control-Allow-Origin", "*");
response.getHeaders().add("Access-Control-Allow-Origin", "*"); response.getHeaders().add("Access-Control-Allow-Headers", "*");
response.getHeaders().add("Access-Control-Allow-Headers", "*"); // "Origin, content-type, Content-type, Accept, authorization, mime-type, filename");
// "Origin, content-type, Content-type, Accept, authorization, mime-type, filename"); response.getHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHeaders().add("Access-Control-Allow-Credentials", "true"); response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
response.getHeaders().add("Access-Control-Allow-Methods", }
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
} }

View File

@ -1,22 +1,22 @@
package org.kar.archidata.filter; package org.kar.archidata.filter;
import org.kar.archidata.model.UserByToken;
import java.security.Principal; import java.security.Principal;
import org.kar.archidata.model.UserByToken;
public class GenericContext implements Principal { public class GenericContext implements Principal {
public UserByToken userByToken; public UserByToken userByToken;
public GenericContext(UserByToken userByToken) { public GenericContext(UserByToken userByToken) {
this.userByToken = userByToken; this.userByToken = userByToken;
} }
@Override @Override
public String getName() { public String getName() {
if (this.userByToken == null) { if (this.userByToken == null) {
return "???"; return "???";
} }
return this.userByToken.name; return this.userByToken.name;
} }
} }

View File

@ -1,48 +1,49 @@
package org.kar.archidata.filter; package org.kar.archidata.filter;
import java.security.Principal;
import org.kar.archidata.model.UserByToken; import org.kar.archidata.model.UserByToken;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
// https://simplapi.wordpress.com/2015/09/19/jersey-jax-rs-securitycontext-in-action/ // https://simplapi.wordpress.com/2015/09/19/jersey-jax-rs-securitycontext-in-action/
class MySecurityContext implements SecurityContext { class MySecurityContext implements SecurityContext {
private final GenericContext contextPrincipale; private final GenericContext contextPrincipale;
private final String sheme; private final String sheme;
public MySecurityContext(UserByToken userByToken, String sheme) { public MySecurityContext(UserByToken userByToken, String sheme) {
this.contextPrincipale = new GenericContext(userByToken); this.contextPrincipale = new GenericContext(userByToken);
this.sheme = sheme; this.sheme = sheme;
} }
@Override @Override
public Principal getUserPrincipal() { public Principal getUserPrincipal() {
return contextPrincipale; return contextPrincipale;
} }
@Override @Override
public boolean isUserInRole(String role) { public boolean isUserInRole(String role) {
if (contextPrincipale.userByToken != null) { if (contextPrincipale.userByToken != null) {
Object value = this.contextPrincipale.userByToken.right.get(role); Object value = this.contextPrincipale.userByToken.right.get(role);
if (value instanceof Boolean ret) { if (value instanceof Boolean ret) {
return ret; return ret;
} }
} }
return false; return false;
} }
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return sheme.equalsIgnoreCase("https"); return sheme.equalsIgnoreCase("https");
} }
@Override @Override
public String getAuthenticationScheme() { public String getAuthenticationScheme() {
if (contextPrincipale.userByToken != null) { if (contextPrincipale.userByToken != null) {
return "Zota"; return "Zota";
} }
return null; return null;
} }
} }

View File

@ -1,21 +1,20 @@
package org.kar.archidata.filter; package org.kar.archidata.filter;
import java.io.IOException;
import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
@Provider @Provider
@PreMatching @PreMatching
public class OptionFilter implements ContainerRequestFilter { public class OptionFilter implements ContainerRequestFilter {
@Override @Override
public void filter(ContainerRequestContext requestContext) throws IOException { public void filter(ContainerRequestContext requestContext) throws IOException {
if (requestContext.getMethod().contentEquals("OPTIONS")) { if (requestContext.getMethod().contentEquals("OPTIONS")) {
requestContext.abortWith(Response.status(Response.Status.NO_CONTENT).build()); requestContext.abortWith(Response.status(Response.Status.NO_CONTENT).build());
} }
} }
} }

View File

@ -25,15 +25,17 @@ public class MigrationEngine {
public MigrationEngine() { public MigrationEngine() {
this(new ArrayList<MigrationInterface>(), null); this(new ArrayList<MigrationInterface>(), null);
} }
/** /**
* Migration engine constructor (specific mode). * Migration engine constructor (specific mode).
* @param datas All the migration ordered. * @param datas All the migration ordered.
* @param init Initialization migration model. * @param init Initialization migration model.
*/ */
public MigrationEngine( List<MigrationInterface> datas, MigrationInterface init) { public MigrationEngine(List<MigrationInterface> datas, MigrationInterface init) {
this.datas = datas; this.datas = datas;
this.init = init; this.init = init;
} }
/** /**
* Add a Migration in the list * Add a Migration in the list
* @param migration Migration to add. * @param migration Migration to add.
@ -41,6 +43,7 @@ public class MigrationEngine {
public void add(MigrationInterface migration) { public void add(MigrationInterface migration) {
this.datas.add(migration); this.datas.add(migration);
} }
/** /**
* Set first initialization class * Set first initialization class
* @param migration migration class for first init. * @param migration migration class for first init.
@ -48,6 +51,7 @@ public class MigrationEngine {
public void setInit(MigrationInterface migration) { public void setInit(MigrationInterface migration) {
init = migration; init = migration;
} }
/** /**
* Get the current version/migration name * Get the current version/migration name
* @return Model represent the last migration. If null then no migration has been done. * @return Model represent the last migration. If null then no migration has been done.
@ -59,7 +63,7 @@ public class MigrationEngine {
try { try {
List<MigrationModel> data = SqlWrapper.gets(MigrationModel.class, false); List<MigrationModel> data = SqlWrapper.gets(MigrationModel.class, false);
if (data == null) { if (data == null) {
LOGGER.error("Can not collect the migration table in the DB:{}" ); LOGGER.error("Can not collect the migration table in the DB:{}");
return null; return null;
} }
if (data.size() == 0) { if (data.size() == 0) {
@ -68,15 +72,16 @@ public class MigrationEngine {
} }
LOGGER.debug("List of migrations:"); LOGGER.debug("List of migrations:");
for (MigrationModel elem : data) { for (MigrationModel elem : data) {
LOGGER.debug(" - date={} name={} end={}", elem.modify_date, elem.name, elem.terminated); LOGGER.debug(" - date={} name={} end={}", elem.modify_date, elem.name, elem.terminated);
} }
return data.get(data.size()-1); return data.get(data.size() - 1);
} catch (Exception ex) { } catch (Exception ex) {
LOGGER.error("Fail to Request migration table in the DB:{}", ex.getMessage()); LOGGER.error("Fail to Request migration table in the DB:{}", ex.getMessage());
ex.printStackTrace(); ex.printStackTrace();
} }
return null; return null;
} }
/** /**
* Process the automatic migration of the system * Process the automatic migration of the system
* @param config SQL connection for the migration * @param config SQL connection for the migration
@ -85,11 +90,11 @@ public class MigrationEngine {
*/ */
public void migrate(DBConfig config) throws InterruptedException, IOException { public void migrate(DBConfig config) throws InterruptedException, IOException {
LOGGER.info("Execute migration ... [BEGIN]"); LOGGER.info("Execute migration ... [BEGIN]");
// STEP 1: Check the DB exist: // STEP 1: Check the DB exist:
LOGGER.info("Verify existance of '{}'", config.getDbName()); LOGGER.info("Verify existance of '{}'", config.getDbName());
boolean exist = SqlWrapper.isDBExist(config.getDbName()); boolean exist = SqlWrapper.isDBExist(config.getDbName());
if(!exist) { if (!exist) {
LOGGER.warn("DB: '{}' DOES NOT EXIST ==> create one", config.getDbName()); LOGGER.warn("DB: '{}' DOES NOT EXIST ==> create one", config.getDbName());
// create the local DB: // create the local DB:
SqlWrapper.createDB(config.getDbName()); SqlWrapper.createDB(config.getDbName());
@ -114,7 +119,7 @@ public class MigrationEngine {
ex.printStackTrace(); ex.printStackTrace();
while (true) { while (true) {
LOGGER.error("Fail to create the local DB SQL model for migaration ==> wait administrator interventions"); LOGGER.error("Fail to create the local DB SQL model for migaration ==> wait administrator interventions");
Thread.sleep(60*60*1000); Thread.sleep(60 * 60 * 1000);
} }
} }
LOGGER.info("Create Table with : {}", sqlQuery.get(0)); LOGGER.info("Create Table with : {}", sqlQuery.get(0));
@ -124,7 +129,7 @@ public class MigrationEngine {
ex.printStackTrace(); ex.printStackTrace();
while (true) { while (true) {
LOGGER.error("Fail to create the local DB model for migaration ==> wait administrator interventions"); LOGGER.error("Fail to create the local DB model for migaration ==> wait administrator interventions");
Thread.sleep(60*60*1000); Thread.sleep(60 * 60 * 1000);
} }
} }
} }
@ -142,15 +147,15 @@ public class MigrationEngine {
// nothing to do the initialization model is alone and it is the first time // nothing to do the initialization model is alone and it is the first time
} else { } else {
// we insert a placeholder to simulate all migration is well done. // we insert a placeholder to simulate all migration is well done.
String placeholderName = this.datas.get(this.datas.size()-1).getName(); String placeholderName = this.datas.get(this.datas.size() - 1).getName();
MigrationModel migrationResult = new MigrationModel(); MigrationModel migrationResult = new MigrationModel();
migrationResult.name = placeholderName; migrationResult.name = placeholderName;
migrationResult.stepId = 0; migrationResult.stepId = 0;
migrationResult.terminated = true; migrationResult.terminated = true;
migrationResult.count = 0; migrationResult.count = 0;
migrationResult.log = "Place-holder for first initialization"; migrationResult.log = "Place-holder for first initialization";
try { try {
migrationResult = SqlWrapper.insert(migrationResult); migrationResult = SqlWrapper.insert(migrationResult);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
@ -158,9 +163,9 @@ public class MigrationEngine {
} }
} else { } else {
if (currentVersion.terminated == false) { if (currentVersion.terminated == false) {
while(true) { while (true) {
LOGGER.error("An error occured in the last migration: '{}' defect @{}/{} ==> wait administrator interventions", currentVersion.name , currentVersion.stepId, currentVersion.count); LOGGER.error("An error occured in the last migration: '{}' defect @{}/{} ==> wait administrator interventions", currentVersion.name, currentVersion.stepId, currentVersion.count);
Thread.sleep(60*60*1000); Thread.sleep(60 * 60 * 1000);
} }
} }
LOGGER.info("Upgrade the system Current version: {}", currentVersion.name); LOGGER.info("Upgrade the system Current version: {}", currentVersion.name);
@ -168,8 +173,8 @@ public class MigrationEngine {
if (currentVersion.name.equals(this.init.getName())) { if (currentVersion.name.equals(this.init.getName())) {
toApply = this.datas; toApply = this.datas;
} else { } else {
for (int iii=0; iii<this.datas.size(); iii++) { for (int iii = 0; iii < this.datas.size(); iii++) {
if ( ! find) { if (!find) {
if (this.datas.get(iii).getName() == currentVersion.name) { if (this.datas.get(iii).getName() == currentVersion.name) {
find = true; find = true;
} }
@ -187,18 +192,19 @@ public class MigrationEngine {
} }
LOGGER.info("Execute migration ... [ END ]"); LOGGER.info("Execute migration ... [ END ]");
} }
public void migrateSingle(DBEntry entry, MigrationInterface elem, int id, int count) { public void migrateSingle(DBEntry entry, MigrationInterface elem, int id, int count) {
LOGGER.info("Migrate: [{}/{}] {} [BEGIN]", id, count, elem.getName()); LOGGER.info("Migrate: [{}/{}] {} [BEGIN]", id, count, elem.getName());
StringBuilder log = new StringBuilder(); StringBuilder log = new StringBuilder();
log.append("Start migration"); log.append("Start migration");
MigrationModel migrationResult = new MigrationModel(); MigrationModel migrationResult = new MigrationModel();
migrationResult.name = elem.getName(); migrationResult.name = elem.getName();
migrationResult.stepId = 0; migrationResult.stepId = 0;
migrationResult.terminated = false; migrationResult.terminated = false;
migrationResult.count = elem.getNumberOfStep(); migrationResult.count = elem.getNumberOfStep();
migrationResult.log = log.toString(); migrationResult.log = log.toString();
try { try {
migrationResult = SqlWrapper.insert(migrationResult); migrationResult = SqlWrapper.insert(migrationResult);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
@ -206,23 +212,24 @@ public class MigrationEngine {
if (elem.applyMigration(entry, log, migrationResult)) { if (elem.applyMigration(entry, log, migrationResult)) {
migrationResult.terminated = true; migrationResult.terminated = true;
try { try {
SqlWrapper.update(migrationResult, migrationResult.id, List.of("terminated")); SqlWrapper.update(migrationResult, migrationResult.id, List.of("terminated"));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} else { } else {
try { try {
log.append("Fail in the migration engine..."); log.append("Fail in the migration engine...");
migrationResult.log = log.toString(); migrationResult.log = log.toString();
SqlWrapper.update(migrationResult, migrationResult.id, List.of("log")); SqlWrapper.update(migrationResult, migrationResult.id, List.of("log"));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
while(true) { while (true) {
LOGGER.error("An error occured in the migration (OUTSIDE detection): '{}' defect @{}/{} ==> wait administrator interventions", migrationResult.name , migrationResult.stepId, migrationResult.count); LOGGER.error("An error occured in the migration (OUTSIDE detection): '{}' defect @{}/{} ==> wait administrator interventions", migrationResult.name, migrationResult.stepId,
migrationResult.count);
try { try {
Thread.sleep(60*60*1000); Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
@ -231,13 +238,13 @@ public class MigrationEngine {
} }
LOGGER.info("Migrate: [{}/{}] {} [ END ]", id, count, elem.getName()); LOGGER.info("Migrate: [{}/{}] {} [ END ]", id, count, elem.getName());
} }
public void revertTo(DBEntry entry, String migrationName) { public void revertTo(DBEntry entry, String migrationName) {
MigrationModel currentVersion = getCurrentVersion(); MigrationModel currentVersion = getCurrentVersion();
List<MigrationInterface> toApply = new ArrayList<>(); List<MigrationInterface> toApply = new ArrayList<>();
boolean find = false; boolean find = false;
for (int iii=this.datas.size()-1; iii>=0; iii--) { for (int iii = this.datas.size() - 1; iii >= 0; iii--) {
if ( ! find) { if (!find) {
if (this.datas.get(iii).getName() == currentVersion.name) { if (this.datas.get(iii).getName() == currentVersion.name) {
find = true; find = true;
} }
@ -254,6 +261,7 @@ public class MigrationEngine {
revertSingle(entry, elem, id, count); revertSingle(entry, elem, id, count);
} }
} }
public void revertSingle(DBEntry entry, MigrationInterface elem, int id, int count) { public void revertSingle(DBEntry entry, MigrationInterface elem, int id, int count) {
LOGGER.info("Revert migration: {} [BEGIN]", elem.getName()); LOGGER.info("Revert migration: {} [BEGIN]", elem.getName());

View File

@ -1,7 +1,7 @@
package org.kar.archidata.migration; package org.kar.archidata.migration;
public class MigrationException extends Exception { public class MigrationException extends Exception {
private static final long serialVersionUID = 20230502L; private static final long serialVersionUID = 20230502L;
} }

View File

@ -8,6 +8,7 @@ public interface MigrationInterface {
* @return Migration name * @return Migration name
*/ */
String getName(); String getName();
/** /**
* Migrate the system to a new version. * Migrate the system to a new version.
* @param entry DB interface for the migration. * @param entry DB interface for the migration.
@ -16,6 +17,7 @@ public interface MigrationInterface {
* @return true if migration is finished. * @return true if migration is finished.
*/ */
boolean applyMigration(DBEntry entry, StringBuilder log, MigrationModel model); boolean applyMigration(DBEntry entry, StringBuilder log, MigrationModel model);
/** /**
* Remove a migration the system to the previous version. * Remove a migration the system to the previous version.
* @param entry DB interface for the migration. * @param entry DB interface for the migration.

View File

@ -3,31 +3,31 @@ package org.kar.archidata.migration;
import org.kar.archidata.annotation.SQLComment; import org.kar.archidata.annotation.SQLComment;
import org.kar.archidata.annotation.SQLDefault; import org.kar.archidata.annotation.SQLDefault;
import org.kar.archidata.annotation.SQLIfNotExists; import org.kar.archidata.annotation.SQLIfNotExists;
import org.kar.archidata.annotation.SQLLimitSize;
import org.kar.archidata.annotation.SQLNotNull;
import org.kar.archidata.annotation.SQLTableName;
import org.kar.archidata.model.GenericTable; import org.kar.archidata.model.GenericTable;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
// For logs only // For logs only
//public static final String TABLE_NAME = "KAR_migration"; //public static final String TABLE_NAME = "KAR_migration";
@SQLTableName ("KAR_migration") @Table(name = "KAR_migration")
@SQLIfNotExists @SQLIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class MigrationModel extends GenericTable{ public class MigrationModel extends GenericTable {
@SQLComment("Name of the migration") @SQLComment("Name of the migration")
@SQLLimitSize(256) @Column(length = 256)
public String name; public String name;
@SQLNotNull @Column(nullable = false)
@SQLDefault("'0'") @SQLDefault("'0'")
@SQLComment("if the migration is well terminated or not") @SQLComment("if the migration is well terminated or not")
public Boolean terminated = false; public Boolean terminated = false;
@SQLComment("index in the migration progression") @SQLComment("index in the migration progression")
public Integer stepId = 0; public Integer stepId = 0;
@SQLComment("number of element in the migration") @SQLComment("number of element in the migration")
public Integer count; public Integer count;
@SQLComment("Log generate by the migration") @SQLComment("Log generate by the migration")
public String log = ""; public String log = "";
} }

View File

@ -12,19 +12,26 @@ import org.slf4j.LoggerFactory;
public class MigrationSqlStep implements MigrationInterface { public class MigrationSqlStep implements MigrationInterface {
final static Logger LOGGER = LoggerFactory.getLogger(MigrationSqlStep.class); final static Logger LOGGER = LoggerFactory.getLogger(MigrationSqlStep.class);
private List<String> actions = new ArrayList<>(); private final List<String> actions = new ArrayList<>();
@Override @Override
public String getName() { public String getName() {
return getClass().getCanonicalName(); return getClass().getCanonicalName();
} }
public void display() {
for (int iii = 0; iii < this.actions.size(); iii++) {
final String action = this.actions.get(iii);
LOGGER.info(" >>>> SQL ACTION : {}/{} ==> \n{}", iii, this.actions.size(), action);
}
}
@Override @Override
public boolean applyMigration(DBEntry entry, StringBuilder log, MigrationModel model) { public boolean applyMigration(final DBEntry entry, final StringBuilder log, final MigrationModel model) {
for (int iii=0; iii<actions.size(); iii++) { for (int iii = 0; iii < this.actions.size(); iii++) {
log.append("action [" + iii + "/" + actions.size() + "]\n"); log.append("action [" + iii + "/" + this.actions.size() + "]\n");
LOGGER.info(" >>>> SQL ACTION : {}/{}", iii, actions.size()); LOGGER.info(" >>>> SQL ACTION : {}/{}", iii, this.actions.size());
String action = actions.get(iii); final String action = this.actions.get(iii);
LOGGER.info("SQL request: ```{}```", action); LOGGER.info("SQL request: ```{}```", action);
log.append("SQL: " + action + "\n"); log.append("SQL: " + action + "\n");
try { try {
@ -33,28 +40,28 @@ public class MigrationSqlStep implements MigrationInterface {
ex.printStackTrace(); ex.printStackTrace();
LOGGER.info("SQL request ERROR: ", ex.getMessage()); LOGGER.info("SQL request ERROR: ", ex.getMessage());
log.append("SQL request ERROR: " + ex.getMessage() + "\n"); log.append("SQL request ERROR: " + ex.getMessage() + "\n");
model.stepId = iii+1; model.stepId = iii + 1;
model.log = log.toString(); model.log = log.toString();
try { try {
SqlWrapper.update(model, model.id, List.of("stepId", "log")); SqlWrapper.update(model, model.id, List.of("stepId", "log"));
} catch (Exception e) { } catch (final Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return false; return false;
} }
log.append("action [" + iii + "/" + actions.size() + "] ==> DONE\n"); log.append("action [" + iii + "/" + this.actions.size() + "] ==> DONE\n");
LOGGER.info(" >>>> SQL ACTION : {}/{} ==> DONE", iii, actions.size()); LOGGER.info(" >>>> SQL ACTION : {}/{} ==> DONE", iii, this.actions.size());
model.stepId = iii+1; model.stepId = iii + 1;
model.log = log.toString(); model.log = log.toString();
try { try {
SqlWrapper.update(model, model.id, List.of("stepId", "log")); SqlWrapper.update(model, model.id, List.of("stepId", "log"));
} catch (Exception e) { } catch (final Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (final InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
@ -63,21 +70,22 @@ public class MigrationSqlStep implements MigrationInterface {
} }
@Override @Override
public boolean revertMigration(DBEntry entry, StringBuilder log) { public boolean revertMigration(final DBEntry entry, final StringBuilder log) {
return false; return false;
} }
public void addAction(String action) { public void addAction(final String action) {
actions.add(action); this.actions.add(action);
} }
public void addClass(Class<?> clazz) throws Exception {
List<String> tmp = SqlWrapper.createTable(clazz, false); public void addClass(final Class<?> clazz) throws Exception {
actions.addAll(tmp); final List<String> tmp = SqlWrapper.createTable(clazz, false);
this.actions.addAll(tmp);
} }
@Override @Override
public int getNumberOfStep() { public int getNumberOfStep() {
return actions.size(); return this.actions.size();
} }
} }

View File

@ -1,28 +1,25 @@
package org.kar.archidata.model; package org.kar.archidata.model;
import org.kar.archidata.annotation.SQLComment; import org.kar.archidata.annotation.SQLComment;
import org.kar.archidata.annotation.SQLIfNotExists; import org.kar.archidata.annotation.SQLIfNotExists;
import org.kar.archidata.annotation.SQLLimitSize;
import org.kar.archidata.annotation.SQLNotNull;
import org.kar.archidata.annotation.SQLTableName;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@SQLTableName ("data") import jakarta.persistence.Column;
import jakarta.persistence.Table;
@Table(name = "data")
@SQLIfNotExists @SQLIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Data extends GenericTable { public class Data extends GenericTable {
@SQLNotNull @Column(length = 128, nullable = false)
@SQLLimitSize(128) @SQLComment("Sha512 of the data")
@SQLComment("Sha512 of the data") public String sha512;
public String sha512; @Column(length = 128, nullable = false)
@SQLNotNull @SQLComment("Mime -type of the media")
@SQLLimitSize(128) public String mimeType;
@SQLComment("Mime -type of the media") @Column(nullable = false)
public String mimeType; @SQLComment("Size in Byte of the data")
@SQLNotNull public Long size;
@SQLComment("Size in Byte of the data")
public Long size;
} }

View File

@ -2,38 +2,40 @@ package org.kar.archidata.model;
import java.sql.Timestamp; import java.sql.Timestamp;
import org.kar.archidata.annotation.SQLAutoIncrement; import org.kar.archidata.annotation.CreationTimestamp;
import org.kar.archidata.annotation.SQLComment; import org.kar.archidata.annotation.SQLComment;
import org.kar.archidata.annotation.SQLCreateTime;
import org.kar.archidata.annotation.SQLDefault; import org.kar.archidata.annotation.SQLDefault;
import org.kar.archidata.annotation.SQLDeleted; import org.kar.archidata.annotation.SQLDeleted;
import org.kar.archidata.annotation.SQLNotNull;
import org.kar.archidata.annotation.SQLNotRead; import org.kar.archidata.annotation.SQLNotRead;
import org.kar.archidata.annotation.SQLPrimaryKey; import org.kar.archidata.annotation.UpdateTimestamp;
import org.kar.archidata.annotation.SQLUpdateTime;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
public class GenericTable { public class GenericTable {
@SQLAutoIncrement // Add AUTO_INCREMENT modifier @Id
@SQLPrimaryKey // Create a PRIMARY KEY based on this field @GeneratedValue(strategy = GenerationType.IDENTITY)
@SQLNotNull @Column(nullable = false, unique = true)
@SQLComment("Primary key of the base") @SQLComment("Primary key of the base")
public Long id = null; public Long id = null;
@SQLNotRead @SQLNotRead
@SQLNotNull @Column(nullable = false)
@SQLDefault("'0'") @SQLDefault("'0'")
@SQLDeleted @SQLDeleted
@SQLComment("When delete, they are not removed, they are just set in a deleted state") @SQLComment("When delete, they are not removed, they are just set in a deleted state")
public Boolean deleted = null; public Boolean deleted = null;
@SQLNotRead @SQLNotRead
@SQLCreateTime @CreationTimestamp
@SQLNotNull @Column(nullable = false)
@SQLComment("Create time of the object") @SQLComment("Create time of the object")
@SQLDefault("CURRENT_TIMESTAMP(3)") @SQLDefault("CURRENT_TIMESTAMP(3)")
public Timestamp create_date = null; public Timestamp create_date = null;
@SQLNotRead @SQLNotRead
@SQLUpdateTime @UpdateTimestamp
@SQLNotNull @Column(nullable = false)
@SQLComment("When update the object") @SQLComment("When update the object")
@SQLDefault("CURRENT_TIMESTAMP(3)") @SQLDefault("CURRENT_TIMESTAMP(3)")
public Timestamp modify_date = null; public Timestamp modify_date = null;
} }

View File

@ -3,21 +3,22 @@ package org.kar.archidata.model;
import java.sql.Timestamp; import java.sql.Timestamp;
import org.kar.archidata.annotation.SQLIfNotExists; import org.kar.archidata.annotation.SQLIfNotExists;
import org.kar.archidata.annotation.SQLNotNull;
import org.kar.archidata.annotation.SQLTableName;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@SQLTableName ("applicationToken") import jakarta.persistence.Column;
import jakarta.persistence.Table;
@Table(name = "applicationToken")
@SQLIfNotExists @SQLIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class GenericToken extends GenericTable { public class GenericToken extends GenericTable {
@SQLNotNull @Column(nullable = false)
public Long parentId; public Long parentId;
@SQLNotNull @Column(nullable = false)
public String name; public String name;
@SQLNotNull @Column(nullable = false)
public Timestamp endValidityTime = null; public Timestamp endValidityTime = null;
@SQLNotNull @Column(nullable = false)
public String token; public String token;
} }

View File

@ -3,6 +3,7 @@ package org.kar.archidata.model;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public record GetToken(String jwt) { public record GetToken(
String jwt) {
} }

View File

@ -1,6 +1,6 @@
package org.kar.archidata.model; package org.kar.archidata.model;
public class Migration extends GenericTable{ public class Migration extends GenericTable {
public String migrationId; public String migrationId;
} }

View File

@ -3,46 +3,38 @@ package org.kar.archidata.model;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
public class Token { public class Token {
public Long id; public Long id;
public Long userId; public Long userId;
public String token; public String token;
public String createTime; public String createTime;
public String endValidityTime; public String endValidityTime;
public Token() { public Token() {}
}
public Token(long id, long userId, String token, String createTime, String endValidityTime) {
public Token(long id, long userId, String token, String createTime, String endValidityTime) { this.id = id;
this.id = id; this.userId = userId;
this.userId = userId; this.token = token;
this.token = token; this.createTime = createTime;
this.createTime = createTime; this.endValidityTime = endValidityTime;
this.endValidityTime = endValidityTime; }
}
public Token(ResultSet rs) {
public Token(ResultSet rs) { int iii = 1;
int iii = 1; try {
try { this.id = rs.getLong(iii++);
this.id = rs.getLong(iii++); this.userId = rs.getLong(iii++);
this.userId = rs.getLong(iii++); this.token = rs.getString(iii++);
this.token = rs.getString(iii++); this.createTime = rs.getString(iii++);
this.createTime = rs.getString(iii++); this.endValidityTime = rs.getString(iii++);
this.endValidityTime = rs.getString(iii++); } catch (SQLException ex) {
} catch (SQLException ex) { ex.printStackTrace();
ex.printStackTrace(); }
} }
}
@Override
@Override public String toString() {
public String toString() { return "Token{" + "id=" + id + ", userId=" + userId + ", token='" + token + '\'' + ", createTime=" + createTime + ", endValidityTime=" + endValidityTime + '}';
return "Token{" + }
"id=" + id +
", userId=" + userId +
", token='" + token + '\'' +
", createTime=" + createTime +
", endValidityTime=" + endValidityTime +
'}';
}
} }

View File

@ -15,35 +15,43 @@ CREATE TABLE `user` (
*/ */
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.List;
import org.kar.archidata.annotation.SQLDefault; import org.kar.archidata.annotation.SQLDefault;
import org.kar.archidata.annotation.SQLIfNotExists; import org.kar.archidata.annotation.SQLIfNotExists;
import org.kar.archidata.annotation.SQLLimitSize; import org.kar.archidata.sqlWrapper.Foreign;
import org.kar.archidata.annotation.SQLNotNull;
import org.kar.archidata.annotation.SQLTableName;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@SQLTableName ("user") import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Table(name = "user")
@SQLIfNotExists @SQLIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends GenericTable { public class User extends GenericTable {
@SQLLimitSize(128) @Column(length = 128)
public String login = null; public String login = null;
public Timestamp lastConnection = null; public Timestamp lastConnection = null;
@SQLDefault("'0'") @SQLDefault("'0'")
@SQLNotNull @Column(nullable = false)
public boolean admin = false; public boolean admin = false;
@SQLDefault("'0'") @SQLDefault("'0'")
@SQLNotNull @Column(nullable = false)
public boolean blocked = false; public boolean blocked = false;
@SQLDefault("'0'") @SQLDefault("'0'")
@SQLNotNull @Column(nullable = false)
public boolean removed = false; public boolean removed = false;
@ManyToOne(fetch = FetchType.LAZY)
public List<Foreign<Data>> covers;
@Override @Override
public String toString() { public String toString() {
return "User [login=" + login + ", last=" + lastConnection + ", admin=" + admin + "]"; return "User [login=" + this.login + ", last=" + this.lastConnection + ", admin=" + this.admin + "]";
} }
} }

View File

@ -3,7 +3,6 @@ package org.kar.archidata.model;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class UserByToken { public class UserByToken {
public static final int TYPE_USER = -1; public static final int TYPE_USER = -1;
public static final int TYPE_APPLICATION = -2; public static final int TYPE_APPLICATION = -2;
@ -11,49 +10,49 @@ public class UserByToken {
public Integer type = null; public Integer type = null;
public Long id = null; public Long id = null;
public Long parentId = null; // FOr application, this is the id of the application, and of user token, this is the USERID public Long parentId = null; // FOr application, this is the id of the application, and of user token, this is the USERID
public String name = null; public String name = null;
// Right map // Right map
public Map<String, Object> right = new HashMap<>(); public Map<String, Object> right = new HashMap<>();
public boolean hasRight(String key, Object value) { public boolean hasRight(String key, Object value) {
if (! this.right.containsKey(key)) { if (!this.right.containsKey(key)) {
return false; return false;
} }
Object data = this.right.get(key); Object data = this.right.get(key);
if (data instanceof Boolean elem) { if (data instanceof Boolean elem) {
if (value instanceof Boolean castVal) { if (value instanceof Boolean castVal) {
if (elem == castVal) { if (elem == castVal) {
return true; return true;
} }
} }
return false; return false;
} }
if (data instanceof String elem) { if (data instanceof String elem) {
if (value instanceof String castVal) { if (value instanceof String castVal) {
if (elem.equals(castVal)) { if (elem.equals(castVal)) {
return true; return true;
} }
} }
return false; return false;
} }
if (data instanceof Long elem) { if (data instanceof Long elem) {
if (value instanceof Long castVal) { if (value instanceof Long castVal) {
if (elem == castVal) { if (elem == castVal) {
return true; return true;
} }
} }
return false; return false;
} }
if (data instanceof Double elem) { if (data instanceof Double elem) {
if (value instanceof Double castVal) { if (value instanceof Double castVal) {
if (elem == castVal) { if (elem == castVal) {
return true; return true;
} }
} }
return false; return false;
} }
return false; return false;
} }
} }

View File

@ -0,0 +1,16 @@
package org.kar.archidata.sqlWrapper;
public class Foreign<T> {
public final Long id;
public final T data;
public Foreign(final Long id) {
this.id = id;
this.data = null;
}
public Foreign(final T data) {
this.id = null;
this.data = data;
}
}

View File

@ -0,0 +1,11 @@
package org.kar.archidata.sqlWrapper;
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalForeinKeyAsList;
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalLink;
public class GenericAddOn {
public static void addGenericAddOn() {
SqlWrapper.addAddOn(new AddOnSQLTableExternalLink());
SqlWrapper.addAddOn(new AddOnSQLTableExternalForeinKeyAsList());
}
}

View File

@ -0,0 +1,43 @@
package org.kar.archidata.sqlWrapper;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
public class QuerryAnd implements QuerryItem {
protected final List<QuerryItem> childs;
public QuerryAnd(List<QuerryItem> childs) {
this.childs = childs;
}
public QuerryAnd(QuerryItem... items) {
this.childs = new ArrayList<>();
for (int iii = 0; iii < items.length; iii++) {
this.childs.add(items[iii]);
}
}
public void generateQuerry(StringBuilder querry, String tableName) {
querry.append(" (");
boolean first = false;
for (QuerryItem elem : this.childs) {
if (first) {
first = false;
} else {
querry.append(" AND ");
}
elem.generateQuerry(querry, tableName);
}
querry.append(")");
}
@Override
public int injectQuerry(PreparedStatement ps, int iii) throws Exception {
for (QuerryItem elem : this.childs) {
iii = elem.injectQuerry(ps, iii);
}
return iii;
}
}

View File

@ -0,0 +1,31 @@
package org.kar.archidata.sqlWrapper;
import java.sql.PreparedStatement;
public class QuerryCondition implements QuerryItem {
private final String key;
private final String comparator;
private final Object value;
public QuerryCondition(String key, String comparator, Object value) {
this.key = key;
this.comparator = comparator;
this.value = value;
}
public void generateQuerry(StringBuilder querry, String tableName) {
querry.append(tableName);
querry.append(".");
querry.append(this.key);
querry.append(" ");
querry.append(this.comparator);
querry.append(" ?");
}
@Override
public int injectQuerry(PreparedStatement ps, int iii) throws Exception {
SqlWrapper.addElement(ps, this.value, iii++);
return iii;
}
}

View File

@ -0,0 +1,9 @@
package org.kar.archidata.sqlWrapper;
import java.sql.PreparedStatement;
public interface QuerryItem {
void generateQuerry(StringBuilder querry, String tableName);
int injectQuerry(PreparedStatement ps, int iii) throws Exception;
}

View File

@ -0,0 +1,35 @@
package org.kar.archidata.sqlWrapper;
import java.sql.PreparedStatement;
import java.util.List;
public class QuerryOr implements QuerryItem {
protected final List<QuerryItem> childs;
public QuerryOr(List<QuerryItem> childs) {
this.childs = childs;
}
public void generateQuerry(StringBuilder querry, String tableName) {
querry.append(" (");
boolean first = false;
for (QuerryItem elem : this.childs) {
if (first) {
first = false;
} else {
querry.append(" OR ");
}
elem.generateQuerry(querry, tableName);
}
querry.append(")");
}
@Override
public int injectQuerry(PreparedStatement ps, int iii) throws Exception {
for (QuerryItem elem : this.childs) {
iii = elem.injectQuerry(ps, iii);
}
return iii;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,10 @@
package org.kar.archidata.sqlWrapper; package org.kar.archidata.sqlWrapper;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public interface SqlWrapperAddOn { public interface SqlWrapperAddOn {
/** /**
@ -8,12 +12,14 @@ public interface SqlWrapperAddOn {
* @return The annotation class * @return The annotation class
*/ */
Class<?> getAnnotationClass(); Class<?> getAnnotationClass();
/** /**
* Get the SQL type that is needed to declare for the specific Field Type. * Get the SQL type that is needed to declare for the specific Field Type.
* @param elem Field to declare. * @param elem Field to declare.
* @return SQL type to create. * @return SQL type to create.
*/ */
String getSQLFieldType(Field elem); String getSQLFieldType(Field elem);
/** /**
* Check if the field is manage by the local add-on * Check if the field is manage by the local add-on
* @param elem Field to inspect. * @param elem Field to inspect.
@ -21,4 +27,17 @@ public interface SqlWrapperAddOn {
*/ */
boolean isCompatibleField(Field elem); boolean isCompatibleField(Field elem);
int insertData(PreparedStatement ps, Object data, int iii) throws SQLException;
// External mean that the type of the object is absolutely not obvious...
boolean isExternal();
int generateQuerry(String tableName, Field elem, StringBuilder querry, String name, List<StateLoad> autoClasify);
int fillFromQuerry(ResultSet rs, Field elem, Object data, int count) throws SQLException, IllegalArgumentException, IllegalAccessException;
boolean canUpdate();
void createTables(String tableName, Field elem, StringBuilder mainTableBuilder, List<String> ListOtherTables, boolean createIfNotExist, boolean createDrop, int fieldId) throws Exception;
} }

View File

@ -0,0 +1,5 @@
package org.kar.archidata.sqlWrapper;
public enum StateLoad {
DISABLE, NORMAL, ARRAY
}

View File

@ -1,8 +0,0 @@
package org.kar.archidata.sqlWrapper;
public record WhereCondition(
String key,
String comparator,
Object Value) {
}

View File

@ -0,0 +1,115 @@
package org.kar.archidata.sqlWrapper.addOn;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.kar.archidata.annotation.addOn.SQLTableExternalLink;
import org.kar.archidata.sqlWrapper.SqlWrapper;
import org.kar.archidata.sqlWrapper.SqlWrapperAddOn;
import org.kar.archidata.sqlWrapper.StateLoad;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddOnSQLTableExternalForeinKeyAsList implements SqlWrapperAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnSQLTableExternalLink.class);
/**
* Convert the list if external id in a string '-' separated
* @param ids List of value (null are removed)
* @return '-' string separated
*/
protected static String getStringOfIds(List<Long> ids) {
List<Long> tmp = new ArrayList<>();
for (Long elem : ids) {
tmp.add(elem);
}
return tmp.stream().map(x -> String.valueOf(x)).collect(Collectors.joining("-"));
}
/**
* extract a list of "-" separated element from a SQL input data.
* @param rs Result Set of the BDD
* @param iii Id in the result set
* @return The list of Long value
* @throws SQLException if an error is generated in the sql request.
*/
protected static List<Long> getListOfIds(ResultSet rs, int iii) throws SQLException {
String trackString = rs.getString(iii);
if (rs.wasNull()) {
return null;
}
List<Long> out = new ArrayList<>();
String[] elements = trackString.split("-");
for (String elem : elements) {
Long tmp = Long.parseLong(elem);
out.add(tmp);
}
return out;
}
@Override
public Class<?> getAnnotationClass() {
return SQLTableExternalLink.class;
}
public String getSQLFieldType(Field elem) {
return "STRING";
}
public boolean isCompatibleField(Field elem) {
SQLTableExternalLink decorators = elem.getDeclaredAnnotation(SQLTableExternalLink.class);
return decorators != null;
}
public int insertData(PreparedStatement ps, Object data, int iii) throws SQLException {
if (data == null) {
ps.setNull(iii++, Types.BIGINT);
} else {
@SuppressWarnings("unchecked")
String dataTmp = getStringOfIds((List<Long>) data);
ps.setString(iii++, dataTmp);
}
return iii++;
}
@Override
public boolean isExternal() {
// TODO Auto-generated method stub
return false;
}
@Override
public int generateQuerry(String tableName, Field elem, StringBuilder querry, String name, List<StateLoad> autoClasify) {
autoClasify.add(StateLoad.ARRAY);
querry.append(" ");
querry.append(tableName);
querry.append(".");
querry.append(name);
return 1;
}
@Override
public int fillFromQuerry(ResultSet rs, Field elem, Object data, int count) throws SQLException, IllegalArgumentException, IllegalAccessException {
List<Long> idList = getListOfIds(rs, count);
elem.set(data, idList);
return 1;
}
@Override
public boolean canUpdate() {
return true;
}
@Override
public void createTables(String tableName, Field elem, StringBuilder mainTableBuilder, List<String> ListOtherTables, boolean createIfNotExist, boolean createDrop, int fieldId) throws Exception {
// TODO Auto-generated method stub
SqlWrapper.createTablesSpecificType(tableName, elem, mainTableBuilder, ListOtherTables, createIfNotExist, createDrop, fieldId, String.class);
}
}

View File

@ -0,0 +1,265 @@
package org.kar.archidata.sqlWrapper.addOn;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.addOn.SQLTableExternalLink;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.sqlWrapper.SqlWrapper;
import org.kar.archidata.sqlWrapper.SqlWrapper.ExceptionDBInterface;
import org.kar.archidata.sqlWrapper.SqlWrapperAddOn;
import org.kar.archidata.sqlWrapper.StateLoad;
import org.kar.archidata.util.ConfigBaseVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnSQLTableExternalLink.class);
/**
* Convert the list if external id in a string '-' separated
* @param ids List of value (null are removed)
* @return '-' string separated
*/
protected static String getStringOfIds(final List<Long> ids) {
final List<Long> tmp = new ArrayList<>(ids);
return tmp.stream().map(String::valueOf).collect(Collectors.joining("-"));
}
/**
* extract a list of "-" separated element from a SQL input data.
* @param rs Result Set of the BDD
* @param iii Id in the result set
* @return The list of Long value
* @throws SQLException if an error is generated in the sql request.
*/
protected static List<Long> getListOfIds(final ResultSet rs, final int iii) throws SQLException {
final String trackString = rs.getString(iii);
if (rs.wasNull()) {
return null;
}
final List<Long> out = new ArrayList<>();
final String[] elements = trackString.split("-");
for (final String elem : elements) {
final Long tmp = Long.parseLong(elem);
out.add(tmp);
}
return out;
}
@Override
public Class<?> getAnnotationClass() {
return SQLTableExternalLink.class;
}
@Override
public String getSQLFieldType(final Field elem) {
return "STRING";
}
@Override
public boolean isCompatibleField(final Field elem) {
final SQLTableExternalLink decorators = elem.getDeclaredAnnotation(SQLTableExternalLink.class);
return decorators != null;
}
@Override
public int insertData(final PreparedStatement ps, final Object data, int iii) throws SQLException {
if (data == null) {
ps.setNull(iii++, Types.BIGINT);
} else {
// TODO: we must check if the model of data in a list of Long ... !!!!
@SuppressWarnings("unchecked")
final String dataTmp = getStringOfIds((List<Long>) data);
ps.setString(iii++, dataTmp);
}
return iii++;
}
@Override
public boolean isExternal() {
// TODO Auto-generated method stub
return false;
}
@Override
public int generateQuerry(final String tableName, final Field elem, final StringBuilder querry, final String name, final List<StateLoad> autoClasify) {
autoClasify.add(StateLoad.ARRAY);
String localName = name;
if (name.endsWith("s")) {
localName = name.substring(0, name.length() - 1);
}
final String tmpVariable = "tmp_" + Integer.toString(autoClasify.size());
querry.append(" (SELECT GROUP_CONCAT(");
querry.append(tmpVariable);
querry.append(".");
querry.append(localName);
querry.append("_id SEPARATOR '-') FROM ");
querry.append(tableName);
querry.append("_link_");
querry.append(localName);
querry.append(" ");
querry.append(tmpVariable);
querry.append(" WHERE ");
querry.append(tmpVariable);
querry.append(".deleted = false AND ");
querry.append(tableName);
querry.append(".id = ");
querry.append(tmpVariable);
querry.append(".");
querry.append(tableName);
querry.append("_id GROUP BY ");
querry.append(tmpVariable);
querry.append(".");
querry.append(tableName);
querry.append("_id ) AS ");
querry.append(name);
querry.append(" ");
/*
" (SELECT GROUP_CONCAT(tmp.data_id SEPARATOR '-')" +
" FROM cover_link_node tmp" +
" WHERE tmp.deleted = false" +
" AND node.id = tmp.node_id" +
" GROUP BY tmp.node_id) AS covers" +
*/
return 1;
}
@Override
public int fillFromQuerry(final ResultSet rs, final Field elem, final Object data, final int count) throws SQLException, IllegalArgumentException, IllegalAccessException {
throw new IllegalAccessException("This Add-on has not the capability to insert data directly in DB");
}
@Override
public boolean canUpdate() {
return false;
}
public static void addLink(final Class<?> clazz, final long localKey, final String table, final long remoteKey) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
long uniqueSQLID = -1;
// real add in the BDD:
try {
// prepare the request:
final String querry = "INSERT INTO " + tableName + "_link_" + table + " (create_date, modify_date, " + tableName + "_id, " + table + "_id)" + " VALUES (" + SqlWrapper.getDBNow() + ", "
+ SqlWrapper.getDBNow() + ", ?, ?)";
final PreparedStatement ps = entry.connection.prepareStatement(querry, Statement.RETURN_GENERATED_KEYS);
int iii = 1;
ps.setLong(iii++, localKey);
ps.setLong(iii++, remoteKey);
// execute the request
final int affectedRows = ps.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating data failed, no rows affected.");
}
// retrieve uid inserted
try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
uniqueSQLID = generatedKeys.getLong(1);
} else {
throw new SQLException("Creating user failed, no ID obtained (1).");
}
} catch (final Exception ex) {
LOGGER.debug("Can not get the UID key inserted ... ");
ex.printStackTrace();
throw new SQLException("Creating user failed, no ID obtained (2).");
}
} catch (final SQLException ex) {
ex.printStackTrace();
throw new ExceptionDBInterface(500, "SQL error: " + ex.getMessage());
} finally {
entry.close();
entry = null;
}
}
public static void removeLink(final Class<?> clazz, final long localKey, final String table, final long remoteKey) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
final String querry = "UPDATE `" + tableName + "_link_" + table + "` SET `modify_date`=" + SqlWrapper.getDBNow() + ", `deleted`=true WHERE `" + tableName + "_id` = ? AND `" + table
+ "_id` = ?";
try {
final PreparedStatement ps = entry.connection.prepareStatement(querry);
int iii = 1;
ps.setLong(iii++, localKey);
ps.setLong(iii++, remoteKey);
ps.executeUpdate();
} catch (final SQLException ex) {
ex.printStackTrace();
throw new ExceptionDBInterface(500, "SQL error: " + ex.getMessage());
} finally {
entry.close();
entry = null;
}
}
@Override
public void createTables(final String tableName, final Field elem, final StringBuilder mainTableBuilder, final List<String> ListOtherTables, final boolean createIfNotExist,
final boolean createDrop, final int fieldId) throws Exception {
final String name = elem.getName();
String localName = name;
if (name.endsWith("s")) {
localName = name.substring(0, name.length() - 1);
}
if (createIfNotExist && createDrop) {
final StringBuilder tableTmp = new StringBuilder();
tableTmp.append("DROP TABLE IF EXISTS `");
tableTmp.append(tableName);
tableTmp.append("_link_");
tableTmp.append(localName);
tableTmp.append("`;");
ListOtherTables.add(tableTmp.toString());
}
final StringBuilder otherTable = new StringBuilder();
otherTable.append("CREATE TABLE `");
otherTable.append(tableName);
otherTable.append("_link_");
otherTable.append(localName);
otherTable.append("`(\n");
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
otherTable.append("\t\t`id` bigint NOT NULL AUTO_INCREMENT,\n");
otherTable.append("\t\t`deleted` tinyint(1) NOT NULL DEFAULT '0',\n");
otherTable.append("\t\t`create_date` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n");
otherTable.append("\t\t`modify_date` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n");
} else {
otherTable.append("\t\t`id` INTEGER PRIMARY KEY AUTOINCREMENT,\n");
otherTable.append("\t\t`deleted` INTEGER NOT NULL DEFAULT '0',\n");
otherTable.append("\t\t`create_date` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,\n");
otherTable.append("\t\t`modify_date` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,\n");
}
otherTable.append("\t\t`");
otherTable.append(tableName);
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
otherTable.append("_id` bigint NOT NULL,\n");
} else {
otherTable.append("_id` INTEGER NOT NULL,\n");
}
otherTable.append("\t\t`");
otherTable.append(localName);
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
otherTable.append("_id` bigint NOT NULL\n");
} else {
otherTable.append("_id` INTEGER NOT NULL\n");
}
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
otherTable.append("\t, PRIMARY KEY (`id`)\n");
}
otherTable.append("\t)");
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
otherTable.append(" ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;\n\n");
}
otherTable.append(";");
ListOtherTables.add(otherTable.toString());
}
}

View File

@ -1,21 +0,0 @@
package org.kar.archidata.sqlWrapper.addOn;
import java.lang.reflect.Field;
import org.kar.archidata.annotation.SQLTableLinkGeneric;
import org.kar.archidata.sqlWrapper.SqlWrapperAddOn;
public class ExternalLink implements SqlWrapperAddOn {
@Override
public Class<?> getAnnotationClass() {
return SQLTableLinkGeneric.class;
}
public String getSQLFieldType(Field elem) {
return "STRING";
}
public boolean isCompatibleField(Field elem) {
SQLTableLinkGeneric decorators = elem.getDeclaredAnnotation(SQLTableLinkGeneric.class);
return decorators != null;
}
}

View File

@ -1,92 +1,94 @@
package org.kar.archidata.util; package org.kar.archidata.util;
public class ConfigBaseVariable { public class ConfigBaseVariable {
static public String tmpDataFolder = System.getenv("DATA_TMP_FOLDER"); static public String tmpDataFolder = System.getenv("DATA_TMP_FOLDER");
static public String dataFolder = System.getenv("DATA_FOLDER"); static public String dataFolder = System.getenv("DATA_FOLDER");
static public String dbType = System.getenv("DB_TYPE"); static public String dbType = System.getenv("DB_TYPE");
static public String dbHost = System.getenv("DB_HOST"); static public String dbHost = System.getenv("DB_HOST");
static public String dbPort = System.getenv("DB_PORT"); static public String dbPort = System.getenv("DB_PORT");
static public String dbUser = System.getenv("DB_USER"); static public String dbUser = System.getenv("DB_USER");
static public String dbKeepConnected = System.getenv("DB_KEEP_CONNECTED"); static public String dbKeepConnected = System.getenv("DB_KEEP_CONNECTED");
static public String dbPassword = System.getenv("DB_PASSWORD"); static public String dbPassword = System.getenv("DB_PASSWORD");
static public String bdDatabase = System.getenv("DB_DATABASE"); static public String bdDatabase = System.getenv("DB_DATABASE");
static public String apiAdress = System.getenv("API_ADDRESS"); static public String apiAdress = System.getenv("API_ADDRESS");
static public String ssoAdress = System.getenv("SSO_ADDRESS"); static public String ssoAdress = System.getenv("SSO_ADDRESS");
static public String ssoToken = System.getenv("SSO_TOKEN"); static public String ssoToken = System.getenv("SSO_TOKEN");
public static String getTmpDataFolder() { public static String getTmpDataFolder() {
if (tmpDataFolder == null) { if (tmpDataFolder == null) {
return "/application/data/tmp"; return "/application/data/tmp";
} }
return tmpDataFolder; return tmpDataFolder;
} }
public static String getMediaDataFolder() { public static String getMediaDataFolder() {
if (dataFolder == null) { if (dataFolder == null) {
return "/application/data/media"; return "/application/data/media";
} }
return dataFolder; return dataFolder;
} }
public static String getDBType() { public static String getDBType() {
if (dbType == null) { if (dbType == null) {
return "mysql"; return "mysql";
} }
return dbType; return dbType;
} }
public static String getDBHost() { public static String getDBHost() {
if (dbHost == null) { if (dbHost == null) {
return "localhost"; return "localhost";
} }
return dbHost; return dbHost;
} }
public static String getDBPort() { public static String getDBPort() {
if (dbPort == null) { if (dbPort == null) {
return "3306"; return "3306";
} }
return dbPort; return dbPort;
} }
public static String getDBLogin() { public static String getDBLogin() {
if (dbUser == null) { if (dbUser == null) {
return "root"; return "root";
} }
return dbUser; return dbUser;
} }
public static String getDBPassword() { public static String getDBPassword() {
if (dbPassword == null) { if (dbPassword == null) {
return "base_db_password"; return "base_db_password";
} }
return dbPassword; return dbPassword;
} }
public static String getDBName() { public static String getDBName() {
if (bdDatabase == null) { if (bdDatabase == null) {
return "unknown"; return "unknown";
} }
return bdDatabase; return bdDatabase;
} }
public static boolean getDBKeepConnected() { public static boolean getDBKeepConnected() {
if (dbKeepConnected == null) { if (dbKeepConnected == null) {
return false; return false;
} }
return Boolean.parseBoolean(dbKeepConnected); return Boolean.parseBoolean(dbKeepConnected);
} }
public static String getlocalAddress() {
if (apiAdress == null) { public static String getlocalAddress() {
return "http://0.0.0.0:80/api/"; if (apiAdress == null) {
} return "http://0.0.0.0:80/api/";
return apiAdress; }
} return apiAdress;
}
public static String getSSOAddress() {
return ssoAdress; public static String getSSOAddress() {
} return ssoAdress;
public static String ssoToken() { }
return ssoToken;
} public static String ssoToken() {
return ssoToken;
}
} }

View File

@ -11,283 +11,283 @@ import java.nio.file.StandardCopyOption;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.sqlWrapper.QuerryAnd;
import org.kar.archidata.sqlWrapper.QuerryCondition;
import org.kar.archidata.sqlWrapper.SqlWrapper; import org.kar.archidata.sqlWrapper.SqlWrapper;
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalLink;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.Response;
public class DataTools { public class DataTools {
static final Logger logger = LoggerFactory.getLogger(DataTools.class); private final static Logger LOGGER = LoggerFactory.getLogger(DataTools.class);
public final static int CHUNK_SIZE = 1024 * 1024; // 1MB chunks public final static int CHUNK_SIZE = 1024 * 1024; // 1MB chunks
public final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks public final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks
/** /**
* Upload some data * Upload some data
*/ */
private static long tmpFolderId = 1; private static long tmpFolderId = 1;
public static void createFolder(String path) throws IOException { public static void createFolder(String path) throws IOException {
if (!Files.exists(java.nio.file.Path.of(path))) { if (!Files.exists(java.nio.file.Path.of(path))) {
logger.info("Create folder: " + path); LOGGER.info("Create folder: " + path);
Files.createDirectories(java.nio.file.Path.of(path)); Files.createDirectories(java.nio.file.Path.of(path));
} }
} }
public static long getTmpDataId() { public static long getTmpDataId() {
return tmpFolderId++; return tmpFolderId++;
} }
public static String getTmpFileInData(long tmpFolderId) {
String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId; public static String getTmpFileInData(long tmpFolderId) {
try { String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId;
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); try {
} catch (IOException e) { createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
e.printStackTrace(); } catch (IOException e) {
} e.printStackTrace();
return filePath; }
} return filePath;
}
public static String getTmpFolder() {
String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++; public static String getTmpFolder() {
try { String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++;
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); try {
} catch (IOException e) { createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
e.printStackTrace(); } catch (IOException e) {
} e.printStackTrace();
return filePath; }
} return filePath;
}
public static String getFileData(long tmpFolderId) {
String filePath = ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator + "data"; public static String getFileData(long tmpFolderId) {
try { String filePath = ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator + "data";
createFolder(ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator); try {
} catch (IOException e) { createFolder(ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator);
e.printStackTrace(); } catch (IOException e) {
} e.printStackTrace();
return filePath; }
} return filePath;
}
public static Data getWithSha512(String sha512) {
try { public static Data getWithSha512(String sha512) {
return SqlWrapper.getWhere(Data.class, "sha512", "=", sha512); try {
return SqlWrapper.getWhere(Data.class, new QuerryCondition("sha512", "=", sha512));
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
public static Data getWithId(long id) { public static Data getWithId(long id) {
try { try {
return SqlWrapper.getWhere(Data.class, "deleted", "=", false, "id", "=", id); return SqlWrapper.getWhere(Data.class, new QuerryAnd(List.of(new QuerryCondition("deleted", "=", false), new QuerryCondition("id", "=", id))));
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
public static Data createNewData(long tmpUID, String originalFileName, String sha512) throws IOException, SQLException { public static Data createNewData(long tmpUID, String originalFileName, String sha512) throws IOException, SQLException {
// determine mime type: // determine mime type:
String mimeType = ""; String mimeType = "";
String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1); String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
switch (extension.toLowerCase()) { switch (extension.toLowerCase()) {
case "jpg": case "jpg":
case "jpeg": case "jpeg":
mimeType = "image/jpeg"; mimeType = "image/jpeg";
break; break;
case "png": case "png":
mimeType = "image/png"; mimeType = "image/png";
break; break;
case "webp": case "webp":
mimeType = "image/webp"; mimeType = "image/webp";
break; break;
case "mka": case "mka":
mimeType = "audio/x-matroska"; mimeType = "audio/x-matroska";
break; break;
case "mkv": case "mkv":
mimeType = "video/x-matroska"; mimeType = "video/x-matroska";
break; break;
case "webm": case "webm":
mimeType = "video/webm"; mimeType = "video/webm";
break; break;
default: default:
throw new IOException("Can not find the mime type of data input: '" + extension + "'"); throw new IOException("Can not find the mime type of data input: '" + extension + "'");
} }
String tmpPath = getTmpFileInData(tmpUID); String tmpPath = getTmpFileInData(tmpUID);
long fileSize = Files.size(Paths.get(tmpPath)); long fileSize = Files.size(Paths.get(tmpPath));
Data out = new Data();; Data out = new Data();
try { ;
out.sha512 = sha512; try {
out.mimeType = mimeType; out.sha512 = sha512;
out.size = fileSize; out.mimeType = mimeType;
out = SqlWrapper.insert(out); out.size = fileSize;
} catch (SQLException ex) { out = SqlWrapper.insert(out);
ex.printStackTrace(); } catch (SQLException ex) {
return null; ex.printStackTrace();
} catch (Exception e) { return null;
} catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
String mediaPath = getFileData(out.id); String mediaPath = getFileData(out.id);
logger.info("src = {}", tmpPath); LOGGER.info("src = {}", tmpPath);
logger.info("dst = {}", mediaPath); LOGGER.info("dst = {}", mediaPath);
Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE); Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE);
logger.info("Move done"); LOGGER.info("Move done");
// all is done the file is correctly installed... // all is done the file is correctly installed...
return out; return out;
} }
public static void undelete(Long id) { public static void undelete(Long id) {
try { try {
SqlWrapper.unsetDelete(Data.class, id); SqlWrapper.unsetDelete(Data.class, id);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
public static String saveTemporaryFile(InputStream uploadedInputStream, long idData) { public static String saveTemporaryFile(InputStream uploadedInputStream, long idData) {
return saveFile(uploadedInputStream, getTmpFileInData(idData)); return saveFile(uploadedInputStream, getTmpFileInData(idData));
} }
public static void removeTemporaryFile(long idData) { public static void removeTemporaryFile(long idData) {
String filepath = getTmpFileInData(idData); String filepath = getTmpFileInData(idData);
if (Files.exists(Paths.get(filepath))) { if (Files.exists(Paths.get(filepath))) {
try { try {
Files.delete(Paths.get(filepath)); Files.delete(Paths.get(filepath));
} catch (IOException e) { } catch (IOException e) {
logger.info("can not delete temporary file : {}", Paths.get(filepath)); LOGGER.info("can not delete temporary file : {}", Paths.get(filepath));
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
// save uploaded file to a defined location on the server // save uploaded file to a defined location on the server
public static String saveFile(InputStream uploadedInputStream, String serverLocation) { public static String saveFile(InputStream uploadedInputStream, String serverLocation) {
String out = ""; String out = "";
try { try {
OutputStream outpuStream = new FileOutputStream(new File( OutputStream outpuStream = new FileOutputStream(new File(serverLocation));
serverLocation)); int read = 0;
int read = 0; byte[] bytes = new byte[CHUNK_SIZE_IN];
byte[] bytes = new byte[CHUNK_SIZE_IN]; MessageDigest md = MessageDigest.getInstance("SHA-512");
MessageDigest md = MessageDigest.getInstance("SHA-512");
outpuStream = new FileOutputStream(new File(serverLocation));
outpuStream = new FileOutputStream(new File(serverLocation)); while ((read = uploadedInputStream.read(bytes)) != -1) {
while ((read = uploadedInputStream.read(bytes)) != -1) { //logger.debug("write {}", read);
//logger.debug("write {}", read); md.update(bytes, 0, read);
md.update(bytes, 0, read); outpuStream.write(bytes, 0, read);
outpuStream.write(bytes, 0, read); }
} LOGGER.info("Flush input stream ... {}", serverLocation);
logger.info("Flush input stream ... {}", serverLocation); outpuStream.flush();
outpuStream.flush(); outpuStream.close();
outpuStream.close(); // create the end of sha512
// create the end of sha512 byte[] sha512Digest = md.digest();
byte[] sha512Digest = md.digest(); // convert in hexadecimal
// convert in hexadecimal out = bytesToHex(sha512Digest);
out = bytesToHex(sha512Digest); uploadedInputStream.close();
uploadedInputStream.close(); } catch (IOException ex) {
} catch (IOException ex) { LOGGER.error("Can not write in temporary file ... ");
logger.error("Can not write in temporary file ... "); ex.printStackTrace();
ex.printStackTrace(); } catch (NoSuchAlgorithmException ex) {
} catch (NoSuchAlgorithmException ex) { LOGGER.error("Can not find sha512 algorithms");
logger.error("Can not find sha512 algorithms"); ex.printStackTrace();
ex.printStackTrace(); }
} return out;
return out; }
}
// curl http://localhost:9993/api/users/3
// curl http://localhost:9993/api/users/3 //@Secured
//@Secured /*
/* @GET
@GET @Path("{id}")
@Path("{id}") //@RolesAllowed("GUEST")
//@RolesAllowed("GUEST") @Produces(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_OCTET_STREAM) public Response retriveData(@HeaderParam("Range") String range, @PathParam("id") Long id) throws Exception {
public Response retriveData(@HeaderParam("Range") String range, @PathParam("id") Long id) throws Exception { return retriveDataFull(range, id, "no-name");
return retriveDataFull(range, id, "no-name"); }
} */
*/
public static String bytesToHex(byte[] bytes) {
public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder();
StringBuilder sb = new StringBuilder(); for (byte b : bytes) {
for (byte b : bytes) { sb.append(String.format("%02x", b));
sb.append(String.format("%02x", b)); }
} return sb.toString();
return sb.toString(); }
}
public static String multipartCorrection(String data) { public static String multipartCorrection(String data) {
if (data == null) { if (data == null) {
return null; return null;
} }
if (data.isEmpty()) { if (data.isEmpty()) {
return null; return null;
} }
if (data.contentEquals("null")) { if (data.contentEquals("null")) {
return null; return null;
} }
return data; return data;
} }
public static <T> Response uploadCover(Class<T> clazz, public static <T> Response uploadCover(Class<T> clazz, Long id, String fileName, InputStream fileInputStream, FormDataContentDisposition fileMetaData) {
Long id, try {
String fileName, // correct input string stream :
InputStream fileInputStream, fileName = multipartCorrection(fileName);
FormDataContentDisposition fileMetaData
) { //public NodeSmall uploadFile(final FormDataMultiPart form) {
try { LOGGER.info("Upload media file: {}", fileMetaData);
// correct input string stream : LOGGER.info(" - id: {}", id);
fileName = multipartCorrection(fileName); LOGGER.info(" - file_name: ", fileName);
LOGGER.info(" - fileInputStream: {}", fileInputStream);
//public NodeSmall uploadFile(final FormDataMultiPart form) { LOGGER.info(" - fileMetaData: {}", fileMetaData);
logger.info("Upload media file: {}", fileMetaData); T media = SqlWrapper.get(clazz, id);
logger.info(" - id: {}", id); if (media == null) {
logger.info(" - file_name: ", fileName); return Response.notModified("Media Id does not exist or removed...").build();
logger.info(" - fileInputStream: {}", fileInputStream); }
logger.info(" - fileMetaData: {}", fileMetaData);
T media = SqlWrapper.get(clazz, id); long tmpUID = getTmpDataId();
if (media == null) { String sha512 = saveTemporaryFile(fileInputStream, tmpUID);
return Response.notModified("Media Id does not exist or removed...").build(); Data data = getWithSha512(sha512);
} if (data == null) {
LOGGER.info("Need to add the data in the BDD ... ");
long tmpUID = getTmpDataId(); try {
String sha512 = saveTemporaryFile(fileInputStream, tmpUID); data = createNewData(tmpUID, fileName, sha512);
Data data = getWithSha512(sha512); } catch (IOException ex) {
if (data == null) { removeTemporaryFile(tmpUID);
logger.info("Need to add the data in the BDD ... "); ex.printStackTrace();
try { return Response.notModified("can not create input media").build();
data = createNewData(tmpUID, fileName, sha512); } catch (SQLException ex) {
} catch (IOException ex) { ex.printStackTrace();
removeTemporaryFile(tmpUID); removeTemporaryFile(tmpUID);
ex.printStackTrace(); return Response.notModified("Error in SQL insertion ...").build();
return Response.notModified("can not create input media").build(); }
} catch (SQLException ex) { } else if (data.deleted == true) {
ex.printStackTrace(); LOGGER.error("Data already exist but deleted");
removeTemporaryFile(tmpUID); undelete(data.id);
return Response.notModified("Error in SQL insertion ...").build(); data.deleted = false;
} } else {
} else if (data.deleted == true) { LOGGER.error("Data already exist ... all good");
logger.error("Data already exist but deleted"); }
undelete(data.id); // Fist step: retrieve all the Id of each parents:...
data.deleted = false; LOGGER.info("Find typeNode");
} else { AddOnSQLTableExternalLink.addLink(clazz, id, "cover", data.id);
logger.error("Data already exist ... all good"); return Response.ok(SqlWrapper.get(clazz, id)).build();
} } catch (Exception ex) {
// Fist step: retrieve all the Id of each parents:... System.out.println("Cat ann unexpected error ... ");
logger.info("Find typeNode"); ex.printStackTrace();
SqlWrapper.addLink(clazz, id, "cover", data.id); }
return Response.ok(SqlWrapper.get(clazz, id)).build(); return Response.serverError().build();
} catch (Exception ex) { }
System.out.println("Cat ann unexpected error ... ");
ex.printStackTrace();
}
return Response.serverError().build();
}
} }

View File

@ -32,53 +32,54 @@ public class JWTWrapper {
private static RSAKey rsaJWK = null;; private static RSAKey rsaJWK = null;;
private static RSAKey rsaPublicJWK = null; private static RSAKey rsaPublicJWK = null;
public static class PublicKey { public static class PublicKey {
public String key; public String key;
public PublicKey(String key) { public PublicKey(String key) {
this.key = key; this.key = key;
} }
public PublicKey() {
} public PublicKey() {}
} }
public static void initLocalTokenRemote(String ssoUri, String application) throws IOException, ParseException {
// check Token: public static void initLocalTokenRemote(String ssoUri, String application) throws IOException, ParseException {
URL obj = new URL(ssoUri + "public_key"); // check Token:
//logger.debug("Request token from: {}", obj); URL obj = new URL(ssoUri + "public_key");
HttpURLConnection con = (HttpURLConnection) obj.openConnection(); //logger.debug("Request token from: {}", obj);
con.setRequestMethod("GET"); HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestProperty("User-Agent", application); con.setRequestMethod("GET");
con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("User-Agent", application);
con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Cache-Control", "no-cache");
con.setRequestProperty("Accept", "application/json"); con.setRequestProperty("Content-Type", "application/json");
String ssoToken = ConfigBaseVariable.ssoToken(); con.setRequestProperty("Accept", "application/json");
if (ssoToken != null) { String ssoToken = ConfigBaseVariable.ssoToken();
con.setRequestProperty("Authorization", "Zota " + ssoToken); if (ssoToken != null) {
} con.setRequestProperty("Authorization", "Zota " + ssoToken);
int responseCode = con.getResponseCode(); }
int responseCode = con.getResponseCode();
//logger.debug("GET Response Code :: {}", responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) { // success //logger.debug("GET Response Code :: {}", responseCode);
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); if (responseCode == HttpURLConnection.HTTP_OK) { // success
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer(); String inputLine;
while ((inputLine = in.readLine()) != null) { StringBuffer response = new StringBuffer();
response.append(inputLine); while ((inputLine = in.readLine()) != null) {
} response.append(inputLine);
in.close(); }
// print result in.close();
//logger.debug(response.toString()); // print result
ObjectMapper mapper = new ObjectMapper(); //logger.debug(response.toString());
PublicKey values = mapper.readValue(response.toString(), PublicKey.class); ObjectMapper mapper = new ObjectMapper();
rsaPublicJWK = RSAKey.parse(values.key); PublicKey values = mapper.readValue(response.toString(), PublicKey.class);
return; rsaPublicJWK = RSAKey.parse(values.key);
} return;
logger.debug("GET JWT validator token not worked response code {} from {} ", responseCode, obj); }
} logger.debug("GET JWT validator token not worked response code {} from {} ", responseCode, obj);
}
public static void initLocalToken(String baseUUID) throws Exception{
public static void initLocalToken(String baseUUID) throws Exception {
// RSA signatures require a public and private RSA key pair, the public key // RSA signatures require a public and private RSA key pair, the public key
// must be made known to the JWS recipient in order to verify the signatures // must be made known to the JWS recipient in order to verify the signatures
try { try {
@ -111,12 +112,14 @@ public class JWTWrapper {
} }
} }
public static String getPublicKeyJson() { public static String getPublicKeyJson() {
if (rsaPublicJWK == null) { if (rsaPublicJWK == null) {
return null; return null;
} }
return rsaPublicJWK.toJSONString(); return rsaPublicJWK.toJSONString();
} }
public static java.security.interfaces.RSAPublicKey getPublicKeyJava() throws JOSEException { public static java.security.interfaces.RSAPublicKey getPublicKeyJava() throws JOSEException {
if (rsaPublicJWK == null) { if (rsaPublicJWK == null) {
return null; return null;
@ -146,21 +149,16 @@ public class JWTWrapper {
*/ */
try { try {
// Create RSA-signer with the private key // Create RSA-signer with the private key
JWSSigner signer = new RSASSASigner(rsaJWK); JWSSigner signer = new RSASSASigner(rsaJWK);
logger.warn("timeOutInMunites= {}", timeOutInMunites); logger.warn("timeOutInMunites= {}", timeOutInMunites);
Date now = new Date(); Date now = new Date();
logger.warn("now = {}", now); logger.warn("now = {}", now);
Date expiration = new Date(new Date().getTime() - 60 * timeOutInMunites * 1000 /* millisecond */); Date expiration = new Date(new Date().getTime() - 60 * timeOutInMunites * 1000 /* millisecond */);
logger.warn("expiration= {}", expiration); logger.warn("expiration= {}", expiration);
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder() JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().subject(Long.toString(userID)).claim("login", userLogin).claim("application", application).issuer(isuer).issueTime(now)
.subject(Long.toString(userID)) .expirationTime(expiration); // Do not ask why we need a "-" here ... this have no meaning
.claim("login", userLogin)
.claim("application", application)
.issuer(isuer)
.issueTime(now)
.expirationTime(expiration); // Do not ask why we need a "-" here ... this have no meaning
// add right if needed: // add right if needed:
if (rights != null && !rights.isEmpty()) { if (rights != null && !rights.isEmpty()) {
builder.claim("right", rights); builder.claim("right", rights);
@ -168,7 +166,7 @@ public class JWTWrapper {
// Prepare JWT with claims set // Prepare JWT with claims set
JWTClaimsSet claimsSet = builder.build(); JWTClaimsSet claimsSet = builder.build();
SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT)/*.keyID(rsaJWK.getKeyID())*/.build(), claimsSet); SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT)/*.keyID(rsaJWK.getKeyID())*/.build(), claimsSet);
// Compute the RSA signature // Compute the RSA signature
signedJWT.sign(signer); signedJWT.sign(signer);
// serialize the output... // serialize the output...
@ -178,7 +176,7 @@ public class JWTWrapper {
} }
return null; return null;
} }
public static JWTClaimsSet validateToken(String signedToken, String isuer, String application) { public static JWTClaimsSet validateToken(String signedToken, String isuer, String application) {
if (rsaPublicJWK == null) { if (rsaPublicJWK == null) {
logger.warn("JWT public key is not present !!!"); logger.warn("JWT public key is not present !!!");
@ -187,18 +185,18 @@ public class JWTWrapper {
try { try {
// On the consumer side, parse the JWS and verify its RSA signature // On the consumer side, parse the JWS and verify its RSA signature
SignedJWT signedJWT = SignedJWT.parse(signedToken); SignedJWT signedJWT = SignedJWT.parse(signedToken);
JWSVerifier verifier = new RSASSAVerifier(rsaPublicJWK); JWSVerifier verifier = new RSASSAVerifier(rsaPublicJWK);
if (!signedJWT.verify(verifier)) { if (!signedJWT.verify(verifier)) {
logger.error("JWT token is NOT verified "); logger.error("JWT token is NOT verified ");
return null; return null;
} }
if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) { if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
logger.error("JWT token is expired now = " + new Date() + " with=" + signedJWT.getJWTClaimsSet().getExpirationTime() ); logger.error("JWT token is expired now = " + new Date() + " with=" + signedJWT.getJWTClaimsSet().getExpirationTime());
return null; return null;
} }
if (!isuer.equals(signedJWT.getJWTClaimsSet().getIssuer())) { if (!isuer.equals(signedJWT.getJWTClaimsSet().getIssuer())) {
logger.error("JWT issuer is wong: '" + isuer + "' != '" + signedJWT.getJWTClaimsSet().getIssuer() + "'" ); logger.error("JWT issuer is wong: '" + isuer + "' != '" + signedJWT.getJWTClaimsSet().getIssuer() + "'");
return null; return null;
} }
if (application != null) { if (application != null) {

View File

@ -1,9 +1,8 @@
package org.kar.archidata.util; package org.kar.archidata.util;
public class PublicKey { public class PublicKey {
public String key; public String key;
public PublicKey(String key) { public PublicKey(String key) {
this.key = key; this.key = key;
} }

View File

@ -31,6 +31,7 @@ public class RESTApi {
public void setToken(String token) { public void setToken(String token) {
this.token = token; this.token = token;
} }
public <T> List<T> gets(Class<T> clazz, String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T> List<T> gets(Class<T> clazz, String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
@ -44,9 +45,10 @@ public class RESTApi {
RESTErrorResponseExeption out = mapper.readValue(httpResponse.body(), RESTErrorResponseExeption.class); RESTErrorResponseExeption out = mapper.readValue(httpResponse.body(), RESTErrorResponseExeption.class);
throw out; throw out;
} }
List<T> out = mapper.readValue(httpResponse.body(), new TypeReference<List<T>>(){}); List<T> out = mapper.readValue(httpResponse.body(), new TypeReference<List<T>>() {});
return out; return out;
} }
public <T> T get(Class<T> clazz, String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T> T get(Class<T> clazz, String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
@ -59,20 +61,21 @@ public class RESTApi {
if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {
//LOGGER.error("catch error from REST API: {}", httpResponse.body()); //LOGGER.error("catch error from REST API: {}", httpResponse.body());
RESTErrorResponseExeption out = mapper.readValue(httpResponse.body(), RESTErrorResponseExeption.class); RESTErrorResponseExeption out = mapper.readValue(httpResponse.body(), RESTErrorResponseExeption.class);
throw new RESTErrorResponseExeption(out.uuid, out.time, out.error, out.message, out.status,out.statusMessage); throw new RESTErrorResponseExeption(out.uuid, out.time, out.error, out.message, out.status, out.statusMessage);
} }
//LOGGER.error("status code: {}", httpResponse.statusCode()); //LOGGER.error("status code: {}", httpResponse.statusCode());
//LOGGER.error("data: {}", httpResponse.body()); //LOGGER.error("data: {}", httpResponse.body());
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T)httpResponse.body(); return (T) httpResponse.body();
} }
T out = mapper.readValue(httpResponse.body(), clazz); T out = mapper.readValue(httpResponse.body(), clazz);
return out; return out;
} }
public <T, U> T post(Class<T> clazz, String urlOffset, U data) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T, U> T post(Class<T> clazz, String urlOffset, U data) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
String body = mapper.writeValueAsString(data); String body = mapper.writeValueAsString(data);
Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset)); Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset));
if (token != null) { if (token != null) {
requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token); requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token);
@ -87,15 +90,16 @@ public class RESTApi {
throw out; throw out;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T)httpResponse.body(); return (T) httpResponse.body();
} }
T out = mapper.readValue(httpResponse.body(), clazz); T out = mapper.readValue(httpResponse.body(), clazz);
return out; return out;
} }
public <T> T postMap(Class<T> clazz, String urlOffset, Map<String, Object> data) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T> T postMap(Class<T> clazz, String urlOffset, Map<String, Object> data) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
String body = mapper.writeValueAsString(data); String body = mapper.writeValueAsString(data);
Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset)); Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset));
if (token != null) { if (token != null) {
requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token); requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token);
@ -108,15 +112,16 @@ public class RESTApi {
throw out; throw out;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T)httpResponse.body(); return (T) httpResponse.body();
} }
T out = mapper.readValue(httpResponse.body(), clazz); T out = mapper.readValue(httpResponse.body(), clazz);
return out; return out;
} }
public <T, U> T put(Class<T> clazz, String urlOffset, U data) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T, U> T put(Class<T> clazz, String urlOffset, U data) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
String body = mapper.writeValueAsString(data); String body = mapper.writeValueAsString(data);
Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset)); Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset));
if (token != null) { if (token != null) {
requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token); requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token);
@ -129,15 +134,16 @@ public class RESTApi {
throw out; throw out;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T)httpResponse.body(); return (T) httpResponse.body();
} }
T out = mapper.readValue(httpResponse.body(), clazz); T out = mapper.readValue(httpResponse.body(), clazz);
return out; return out;
} }
public <T> T putMap(Class<T> clazz, String urlOffset, Map<String, Object> data) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T> T putMap(Class<T> clazz, String urlOffset, Map<String, Object> data) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
String body = mapper.writeValueAsString(data); String body = mapper.writeValueAsString(data);
Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset)); Builder requestBuilding = HttpRequest.newBuilder().uri(URI.create(this.baseUrl + urlOffset));
if (token != null) { if (token != null) {
requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token); requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + token);
@ -150,11 +156,12 @@ public class RESTApi {
throw out; throw out;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T)httpResponse.body(); return (T) httpResponse.body();
} }
T out = mapper.readValue(httpResponse.body(), clazz); T out = mapper.readValue(httpResponse.body(), clazz);
return out; return out;
} }
public <T, U> T delete(Class<T> clazz, String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException { public <T, U> T delete(Class<T> clazz, String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
@ -169,7 +176,7 @@ public class RESTApi {
throw out; throw out;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T)httpResponse.body(); return (T) httpResponse.body();
} }
T out = mapper.readValue(httpResponse.body(), clazz); T out = mapper.readValue(httpResponse.body(), clazz);
return out; return out;

View File

@ -0,0 +1,44 @@
package test.kar.archidata;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.kar.archidata.sqlWrapper.SqlWrapper;
import org.kar.archidata.util.RESTApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TestBase {
final static Logger logger = LoggerFactory.getLogger(TestBase.class);
static RESTApi api = null;
@BeforeAll
public static void configureWebServer() throws Exception {
logger.info("Create DB");
final String dbName = "sdfsdfsdfsfsdfsfsfsfsdfsdfsd";
boolean data = SqlWrapper.isDBExist(dbName);
logger.error("exist: {}", data);
data = SqlWrapper.createDB(dbName);
logger.error("create: {}", data);
data = SqlWrapper.isDBExist(dbName);
logger.error("exist: {}", data);
}
@AfterAll
public static void stopWebServer() throws InterruptedException {
logger.info("Kill the web server");
// TODO: do it better...
}
@Order(1)
@Test
public void checkSimpleTestError() throws Exception {
Assertions.assertEquals("lkjlkjlkjlk", "alive and kicking");
}
}