diff --git a/README.md b/README.md index 909cc95..f7f32cc 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Read instruction for tocken in ~/.m2/setting.xml release: +export PATH=/usr/lib/jvm/java-18-openjdk/bin:$PATH mvn install mvn deploy diff --git a/pom.xml b/pom.xml index 1a61609..80dd7f6 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 kangaroo-and-rabbit archidata - 0.1.2 + 0.1.3 2.1 2.32 diff --git a/src/org/kar/archidata/GlobalConfiguration.java b/src/org/kar/archidata/GlobalConfiguration.java index 7e18866..65a46c5 100644 --- a/src/org/kar/archidata/GlobalConfiguration.java +++ b/src/org/kar/archidata/GlobalConfiguration.java @@ -4,7 +4,7 @@ import org.kar.archidata.db.DBConfig; import org.kar.archidata.util.ConfigBaseVariable; public class GlobalConfiguration { - public static DBConfig dbConfig = null;; + public static DBConfig dbConfig = null; static { dbConfig = new DBConfig(ConfigBaseVariable.getDBHost(), diff --git a/src/org/kar/archidata/SqlWrapper.java b/src/org/kar/archidata/SqlWrapper.java index 34e7d9e..d16ae6a 100644 --- a/src/org/kar/archidata/SqlWrapper.java +++ b/src/org/kar/archidata/SqlWrapper.java @@ -520,9 +520,42 @@ public class SqlWrapper { } } + static void addElement(PreparedStatement ps, Object value, int iii) throws Exception { + if (value.getClass() == Long.class) { + ps.setLong(iii, (Long)value); + } else if (value.getClass() == Integer.class) { + ps.setInt(iii, (Integer)value); + } else if (value.getClass() == String.class) { + ps.setString(iii, (String)value); + } else if (value.getClass() == Short.class) { + ps.setShort(iii, (Short)value); + } else if (value.getClass() == Byte.class) { + ps.setByte(iii, (Byte)value); + } else if (value.getClass() == Float.class) { + ps.setFloat(iii, (Float)value); + } else if (value.getClass() == Double.class) { + ps.setDouble(iii, (Double)value); + } else if (value.getClass() == Boolean.class) { + ps.setBoolean(iii, (Boolean)value); + } else if (value.getClass() == Boolean.class) { + ps.setBoolean(iii, (Boolean)value); + } else if (value.getClass() == Timestamp.class) { + ps.setTimestamp(iii, (Timestamp)value); + } else if (value.getClass() == Date.class) { + ps.setDate(iii, (Date)value); + } else { + throw new Exception("Not manage type ==> need to add it ..."); + } + } + public static T getWith(Class clazz, String key, String value) throws Exception { - //public static NodeSmall createNode(String typeInNode, String name, String description, Long parentId) { - + return getWhere(clazz, key, "=", value); + } + + public static T getWhere(Class clazz, String key, String operator, Object value ) throws Exception { + return getWhere(clazz, key, operator, value, null, null, null); + } + public static T getWhere(Class clazz, String key, String operator, Object value, String key2, String operator2, Object value2 ) throws Exception { DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); T out = null; // real add in the BDD: @@ -535,13 +568,8 @@ public class SqlWrapper { //query.append(" SET "); boolean firstField = true; - Field primaryKeyField = null; int count = 0; for (Field elem : clazz.getFields()) { - boolean primaryKey = elem.getDeclaredAnnotationsByType(SQLPrimaryKey.class).length != 0; - if (primaryKey) { - primaryKeyField = elem; - } ModelLink linkGeneric = getLinkMode(elem); if (linkGeneric != ModelLink.NONE) { continue; @@ -564,17 +592,28 @@ public class SqlWrapper { query.append(" "); query.append(tableName); query.append("."); + query.append(name); } - query.append("\n FROM `"); + query.append(" FROM `"); query.append(tableName); query.append("` "); - query.append("\n WHERE "); + query.append(" WHERE "); query.append(tableName); query.append("."); query.append(key); - query.append(" = ?"); - query.append("\n LIMIT 1 "); + query.append(" "); + query.append(operator); + query.append(" ?"); + if (key2 != null) { + query.append(" AND "); + query.append(tableName); + query.append("."); + query.append(key2); + query.append(" "); + query.append(operator2); + query.append(" ?"); + } /* query.append(" AND "); query.append(tableName); @@ -585,7 +624,10 @@ public class SqlWrapper { // prepare the request: PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS); int iii = 1; - ps.setString(iii++, value); + addElement(ps, value, iii++); + if (key2 != null) { + addElement(ps, value2, iii++); + } // execute the request ResultSet rs = ps.executeQuery(); while (rs.next()) { @@ -621,11 +663,9 @@ public class SqlWrapper { entry = null; return out; } - - public static T getWhere(Class clazz, String key, String operator, Object value ) throws Exception { - + public static List getsWhere(Class clazz, String key, String operator, Object value ) throws Exception { DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - T out = null; + List outs = new ArrayList<>(); // real add in the BDD: try { String tableName = getTableName(clazz); @@ -683,31 +723,7 @@ public class SqlWrapper { // prepare the request: PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS); int iii = 1; - if (value.getClass() == Long.class) { - ps.setLong(iii++, (Long)value); - } else if (value.getClass() == Integer.class) { - ps.setInt(iii++, (Integer)value); - } else if (value.getClass() == String.class) { - ps.setString(iii++, (String)value); - } else if (value.getClass() == Short.class) { - ps.setShort(iii++, (Short)value); - } else if (value.getClass() == Byte.class) { - ps.setByte(iii++, (Byte)value); - } else if (value.getClass() == Float.class) { - ps.setFloat(iii++, (Float)value); - } else if (value.getClass() == Double.class) { - ps.setDouble(iii++, (Double)value); - } else if (value.getClass() == Boolean.class) { - ps.setBoolean(iii++, (Boolean)value); - } else if (value.getClass() == Boolean.class) { - ps.setBoolean(iii++, (Boolean)value); - } else if (value.getClass() == Timestamp.class) { - ps.setTimestamp(iii++, (Timestamp)value); - } else if (value.getClass() == Date.class) { - ps.setDate(iii++, (Date)value); - } else { - throw new Exception("Not manage type ==> need to add it ..."); - } + addElement(ps, value, iii++); // execute the request ResultSet rs = ps.executeQuery(); while (rs.next()) { @@ -733,7 +749,8 @@ public class SqlWrapper { setValueFromDb(elem.getType(), data, count, elem, rs); count++; } - out = (T)data; + T out = (T)data; + outs.add(out); } } catch (SQLException ex) { @@ -741,7 +758,7 @@ public class SqlWrapper { } entry.disconnect(); entry = null; - return out; + return outs; } public static T get(Class clazz, long id) throws Exception { @@ -1021,6 +1038,22 @@ public class SqlWrapper { entry.disconnect(); } } + public static void unsetDelete(Class clazz, long id) throws Exception { + String tableName = getTableName(clazz); + DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); + String query = "UPDATE `" + tableName + "` SET `modify_date`=now(3), `deleted`=false WHERE `id` = ?"; + try { + PreparedStatement ps = entry.connection.prepareStatement(query); + int iii = 1; + ps.setLong(iii++, id); + ps.executeUpdate(); + } catch (SQLException ex) { + ex.printStackTrace(); + throw new ExceptionDBInterface(500, "SQL error: " + ex.getMessage()); + } finally { + entry.disconnect(); + } + } public static String createTable(Class clazz) throws Exception { String tableName = getTableName(clazz); diff --git a/src/org/kar/archidata/UpdateJwtPublicKey.java b/src/org/kar/archidata/UpdateJwtPublicKey.java index a8e0594..74c6897 100644 --- a/src/org/kar/archidata/UpdateJwtPublicKey.java +++ b/src/org/kar/archidata/UpdateJwtPublicKey.java @@ -6,12 +6,6 @@ import org.kar.archidata.util.JWTWrapper; public class UpdateJwtPublicKey extends Thread { boolean kill = false; public void run() { - try { - Thread.sleep(1000*20, 0); - } catch (InterruptedException e2) { - // TODO Auto-generated catch block - e2.printStackTrace(); - } while (this.kill == false) { // need to uppgrade when server call us... try { diff --git a/src/org/kar/archidata/UserDB.java b/src/org/kar/archidata/UserDB.java index d638e1e..aacf29b 100755 --- a/src/org/kar/archidata/UserDB.java +++ b/src/org/kar/archidata/UserDB.java @@ -13,76 +13,17 @@ public class UserDB { public static User getUsers(long userId) throws Exception { return SqlWrapper.get(User.class, userId); - /* - DBEntry entry = new DBEntry(WebLauncher.dbConfig); - String query = "SELECT * FROM user WHERE id = ?"; - try { - PreparedStatement ps = entry.connection.prepareStatement(query); - ps.setLong(1, userId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - User out = new User(rs); - entry.disconnect(); - return out; - } - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - entry.disconnect(); - return null; - */ } public static User getUserOrCreate(long userId, String userLogin) throws Exception { User user = getUsers(userId); if (user != null) { - /* - boolean blocked = false; - boolean removed = false; - if (user.email != userOAuth.email || user.login != userOAuth.login || user.blocked != blocked || user.removed != removed) { - updateUsersInfoFromOAuth(userOAuth.id, userOAuth.email, userOAuth.login, blocked, removed); - } else { - updateUsersConnectionTime(userOAuth.id); - } - return getUsers(userOAuth.id); - */ return user; } createUsersInfoFromOAuth(userId, userLogin); return getUsers(userId); } -/* - private static void updateUsersConnectionTime(long userId) { - DBEntry entry = new DBEntry(WebLauncher.dbConfig); - String query = "UPDATE `user` SET `lastConnection`=now(3) WHERE `id` = ?"; - try { - PreparedStatement ps = entry.connection.prepareStatement(query); - ps.setLong(1, userId); - ps.executeUpdate(); - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - entry.disconnect(); - } - - private static void updateUsersInfoFromOAuth(long userId, String email, String login, boolean blocked, boolean removed) { - DBEntry entry = new DBEntry(WebLauncher.dbConfig); - String query = "UPDATE `user` SET `login`=?, `email`=?, `lastConnection`=now(3), `blocked`=?, `removed`=? WHERE id = ?"; - try { - PreparedStatement ps = entry.connection.prepareStatement(query); - ps.setString(1, login); - ps.setString(2, email); - ps.setString(3, blocked ? "TRUE" : "FALSE"); - ps.setString(4, removed ? "TRUE" : "FALSE"); - ps.setLong(5, userId); - ps.executeUpdate(); - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - entry.disconnect(); - } - */ private static void createUsersInfoFromOAuth(long userId, String login) { DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); diff --git a/src/org/kar/archidata/annotation/SQLForeignKey.java b/src/org/kar/archidata/annotation/SQLForeignKey.java new file mode 100644 index 0000000..be8189d --- /dev/null +++ b/src/org/kar/archidata/annotation/SQLForeignKey.java @@ -0,0 +1,12 @@ +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 SQLForeignKey { + String value(); +} diff --git a/src/org/kar/archidata/annotation/security/PermitTokenInURI.java b/src/org/kar/archidata/annotation/security/PermitTokenInURI.java index ce9f632..09d2442 100644 --- a/src/org/kar/archidata/annotation/security/PermitTokenInURI.java +++ b/src/org/kar/archidata/annotation/security/PermitTokenInURI.java @@ -5,7 +5,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @NameBinding diff --git a/src/org/kar/archidata/api/DataResource.java b/src/org/kar/archidata/api/DataResource.java new file mode 100644 index 0000000..519c487 --- /dev/null +++ b/src/org/kar/archidata/api/DataResource.java @@ -0,0 +1,429 @@ +package org.kar.archidata.api; + +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.kar.archidata.filter.GenericContext; +import org.kar.archidata.model.Data; +import org.kar.archidata.SqlWrapper; +import org.kar.archidata.annotation.security.PermitTokenInURI; +import org.kar.archidata.annotation.security.RolesAllowed; +import org.kar.archidata.util.ConfigBaseVariable; + +import javax.imageio.ImageIO; +import javax.ws.rs.*; +import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.StreamingOutput; +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://gist.github.com/aitoroses/4f7a2b197b732a6a691d + +@Path("/data") +@Produces({MediaType.APPLICATION_JSON}) +public class DataResource { + private final static int CHUNK_SIZE = 1024 * 1024; // 1MB chunks + private final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks + /** + * Upload some datas + */ + private static long tmpFolderId = 1; + + private static void createFolder(String path) throws IOException { + if (!Files.exists(java.nio.file.Path.of(path))) { + //Log.print("Create folder: " + path); + Files.createDirectories(java.nio.file.Path.of(path)); + } + } + + public static long getTmpDataId() { + return tmpFolderId++; + } + + public static String getTmpFileInData(long tmpFolderId) { + String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId; + try { + createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); + } catch (IOException e) { + e.printStackTrace(); + } + return filePath; + } + + public static String getFileData(long tmpFolderId) { + String filePath = ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator + "data"; + try { + createFolder(ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator); + } catch (IOException e) { + e.printStackTrace(); + } + return filePath; + } + + public static Data getWithSha512(String sha512) { + System.out.println("find sha512 = " + sha512); + try { + return SqlWrapper.getWhere(Data.class, "sha512", "=", sha512); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + public static Data getWithId(long id) { + System.out.println("find id = " + id); + try { + return SqlWrapper.get(Data.class, id); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + public static Data createNewData(long tmpUID, String originalFileName, String sha512) throws IOException { + // determine mime type: + Data injectedData = new Data(); + String mimeType = ""; + String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1); + switch (extension.toLowerCase()) { + case "jpg": + case "jpeg": + mimeType = "image/jpeg"; + break; + case "png": + mimeType = "image/png"; + break; + case "webp": + mimeType = "image/webp"; + break; + case "mka": + mimeType = "audio/x-matroska"; + break; + case "mkv": + mimeType = "video/x-matroska"; + break; + case "webm": + mimeType = "video/webm"; + break; + default: + throw new IOException("Can not find the mime type of data input: '" + extension + "'"); + } + injectedData.mimeType = mimeType; + injectedData.sha512 = sha512; + String tmpPath = getTmpFileInData(tmpUID); + injectedData.size = Files.size(Paths.get(tmpPath)); + + try { + injectedData = SqlWrapper.insert(injectedData); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + String mediaPath = getFileData(injectedData.id); + System.out.println("src = " + tmpPath); + System.out.println("dst = " + mediaPath); + Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE); + System.out.println("Move done"); + return injectedData; + } + + public static String saveTemporaryFile(InputStream uploadedInputStream, long idData) { + return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData)); + } + + public static void removeTemporaryFile(long idData) { + String filepath = DataResource.getTmpFileInData(idData); + if (Files.exists(Paths.get(filepath))) { + try { + Files.delete(Paths.get(filepath)); + } catch (IOException e) { + System.out.println("can not delete temporary file : " + Paths.get(filepath)); + e.printStackTrace(); + } + } + } + + // save uploaded file to a defined location on the server + static String saveFile(InputStream uploadedInputStream, String serverLocation) { + String out = ""; + try { + OutputStream outpuStream = new FileOutputStream(new File( + serverLocation)); + int read = 0; + byte[] bytes = new byte[CHUNK_SIZE_IN]; + MessageDigest md = MessageDigest.getInstance("SHA-512"); + + outpuStream = new FileOutputStream(new File(serverLocation)); + while ((read = uploadedInputStream.read(bytes)) != -1) { + //System.out.println("write " + read); + md.update(bytes, 0, read); + outpuStream.write(bytes, 0, read); + } + System.out.println("Flush input stream ... " + serverLocation); + System.out.flush(); + outpuStream.flush(); + outpuStream.close(); + // create the end of sha512 + byte[] sha512Digest = md.digest(); + // convert in hexadecimal + out = bytesToHex(sha512Digest); + uploadedInputStream.close(); + } catch (IOException ex) { + System.out.println("Can not write in temporary file ... "); + ex.printStackTrace(); + } catch (NoSuchAlgorithmException ex) { + System.out.println("Can not find sha512 algorithms"); + ex.printStackTrace(); + } + return out; + } + + public static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + + public Data getSmall(Long id) { + try { + return SqlWrapper.get(Data.class, id); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + @POST + @Path("/upload/") + @Consumes({MediaType.MULTIPART_FORM_DATA}) + @RolesAllowed("ADMIN") + public Response uploadFile(@Context SecurityContext sc, @FormDataParam("file") InputStream fileInputStream, @FormDataParam("file") FormDataContentDisposition fileMetaData) { + GenericContext gc = (GenericContext) sc.getUserPrincipal(); + System.out.println("==================================================="); + System.out.println("== DATA uploadFile " + (gc==null?"null":gc.user)); + System.out.println("==================================================="); + //public NodeSmall uploadFile(final FormDataMultiPart form) { + System.out.println("Upload file: "); + String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++; + try { + createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); + } catch (IOException e) { + e.printStackTrace(); + } + saveFile(fileInputStream, filePath); + return Response.ok("Data uploaded successfully !!").build(); + //return null; + } + + @GET + @Path("{id}") + @PermitTokenInURI + @RolesAllowed("USER") + @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 { + GenericContext gc = (GenericContext) sc.getUserPrincipal(); + //System.out.println("==================================================="); + System.out.println("== DATA retriveDataId ? id=" + id + " user=" + (gc==null?"null":gc.user)); + //System.out.println("==================================================="); + Data value = getSmall(id); + if (value == null) { + Response.status(404). + entity("media NOT FOUND: " + id). + type("text/plain"). + build(); + } + return buildStream(ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data", range, value.mimeType); + } + + @GET + @Path("thumbnail/{id}") + @RolesAllowed("USER") + @PermitTokenInURI + @Produces(MediaType.APPLICATION_OCTET_STREAM) + //@CacheMaxAge(time = 10, unit = TimeUnit.DAYS) + public Response retriveDataThumbnailId(@Context SecurityContext sc, + @QueryParam(HttpHeaders.AUTHORIZATION) String token, + @HeaderParam("Range") String range, + @PathParam("id") Long id) throws Exception { + GenericContext gc = (GenericContext) sc.getUserPrincipal(); + //System.out.println("==================================================="); + //System.out.println("== DATA retriveDataThumbnailId ? " + (gc==null?"null":gc.user)); + //System.out.println("==================================================="); + Data value = getSmall(id); + if (value == null) { + return Response.status(404). + entity("media NOT FOUND: " + id). + type("text/plain"). + build(); + } + String filePathName = ConfigBaseVariable.getMediaDataFolder() + File.separator + id + File.separator + "data"; + if ( value.mimeType.contentEquals("image/jpeg") + || value.mimeType.contentEquals("image/png") + // || value.mimeType.contentEquals("image/webp") + ) { + // reads input image + //System.out.println("Read path: " + filePathName); + File inputFile = new File(filePathName); + if (!inputFile.exists()) { + return Response.status(500). + entity("Internal Error: Media is NOT FOUNDABLE: " + id). + type("text/plain"). + build(); + } + BufferedImage inputImage = ImageIO.read(inputFile); + int scaledWidth = 250; + int scaledHeight = (int)((float)inputImage.getHeight() / (float)inputImage.getWidth() * (float) scaledWidth); + // 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) { + e.printStackTrace(); + return Response.status(500). + entity("Internal Error: resize fail: " + e.getMessage()). + type("text/plain"). + build(); + } + byte[] imageData = baos.toByteArray(); + //Response.ok(new ByteArrayInputStream(imageData)).build(); + Response.ResponseBuilder out = Response.ok(imageData) + .header(HttpHeaders.CONTENT_LENGTH, imageData.length); + out.type("image/jpeg"); + // TODO: move this in a decorator !!! + CacheControl cc = new CacheControl(); + cc.setMaxAge(3600); + cc.setNoCache(false); + out.cacheControl(cc); + return out.build(); + } + return buildStream(filePathName, range, value.mimeType); + } + //@Secured + @GET + @Path("{id}/{name}") + @PermitTokenInURI + @RolesAllowed("USER") + @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 { + GenericContext gc = (GenericContext) sc.getUserPrincipal(); + //System.out.println("==================================================="); + System.out.println("== DATA retriveDataFull ? id=" + id + " user=" + (gc==null?"null":gc.user)); + //System.out.println("==================================================="); + Data value = getSmall(id); + if (value == null) { + Response.status(404). + entity("media NOT FOUND: " + id). + type("text/plain"). + build(); + } + 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 + * + * @param range range header + * @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); + //System.out.println("request range : " + range); + // range not requested : Firefox does not send range headers + if (range == null) { + final StreamingOutput output = new StreamingOutput() { + @Override + public void write(OutputStream out) { + try (FileInputStream in = new FileInputStream(file)) { + byte[] buf = new byte[1024 * 1024]; + int len; + while ((len = in.read(buf)) != -1) { + try { + out.write(buf, 0, len); + out.flush(); + //System.out.println("---- wrote " + len + " bytes file ----"); + } catch (IOException ex) { + System.out.println("remote close connection"); + break; + } + } + } catch (IOException ex) { + throw new InternalServerErrorException(ex); + } + } + }; + Response.ResponseBuilder out = Response.ok(output) + .header(HttpHeaders.CONTENT_LENGTH, file.length()); + if (mimeType != null) { + out.type(mimeType); + } + return out.build(); + + } + + String[] ranges = range.split("=")[1].split("-"); + final long from = Long.parseLong(ranges[0]); + + //System.out.println("request range : " + ranges.length); + //Chunk media if the range upper bound is unspecified. Chrome, Opera sends "bytes=0-" + long to = CHUNK_SIZE + from; + if (ranges.length == 1) { + to = 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()); + //System.out.println("responseRange : " + responseRange); + final RandomAccessFile raf = new RandomAccessFile(file, "r"); + raf.seek(from); + + final long len = to - from + 1; + 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) + .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 { + SqlWrapper.unsetDelete(Data.class, id); + } + +} diff --git a/src/org/kar/archidata/api/MediaStreamer.java b/src/org/kar/archidata/api/MediaStreamer.java new file mode 100644 index 0000000..1ad626d --- /dev/null +++ b/src/org/kar/archidata/api/MediaStreamer.java @@ -0,0 +1,56 @@ +package org.kar.archidata.api; + +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +public class MediaStreamer implements StreamingOutput { + private final int CHUNK_SIZE = 1024 * 1024; // 1MB chunks + final byte[] buf = new byte[CHUNK_SIZE]; + private long length; + private RandomAccessFile raf; + + public MediaStreamer(long length, RandomAccessFile raf) throws IOException { + //System.out.println("request stream of " + length / 1024 + " data"); + if (length<0) { + throw new IOException("Wrong size of the file to stream: " + length); + } + this.length = length; + this.raf = raf; + } + + @Override + public void write(OutputStream outputStream) { + try { + while (length != 0) { + int read = raf.read(buf, 0, buf.length > length ? (int) length : buf.length); + try { + outputStream.write(buf, 0, read); + } catch (IOException ex) { + System.out.println("remote close connection"); + break; + } + length -= read; + } + } catch (IOException ex) { + throw new InternalServerErrorException(ex); + } catch (WebApplicationException ex) { + throw new InternalServerErrorException(ex); + } finally { + try { + raf.close(); + } catch (IOException ex) { + ex.printStackTrace(); + throw new InternalServerErrorException(ex); + } + } + } + + public long getLenth() { + return length; + } + +} diff --git a/src/org/kar/archidata/model/Data.java b/src/org/kar/archidata/model/Data.java index a34617a..eabda46 100644 --- a/src/org/kar/archidata/model/Data.java +++ b/src/org/kar/archidata/model/Data.java @@ -3,30 +3,29 @@ package org.kar.archidata.model; import java.sql.ResultSet; import java.sql.SQLException; -public class Data { - public Long id; - public boolean deleted; +import org.kar.archidata.annotation.SQLComment; +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 com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@SQLTableName ("data") +@SQLIfNotExists +@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) +public class Data extends GenericTable { + + @SQLNotNull + @SQLLimitSize(128) + @SQLComment("Sha512 of the data") public String sha512; + @SQLNotNull + @SQLLimitSize(128) + @SQLComment("Mime -type of the media") public String mimeType; + @SQLNotNull + @SQLComment("Size in Byte of the data") public Long size; - - public Data() { - - } - - public Data(ResultSet rs) { - int iii = 1; - try { - this.id = rs.getLong(iii++); - this.deleted = rs.getBoolean(iii++); - this.sha512 = rs.getString(iii++); - this.mimeType = rs.getString(iii++); - this.size = rs.getLong(iii++); - if (rs.wasNull()) { - this.size = null; - } - } catch (SQLException ex) { - ex.printStackTrace(); - } - } } diff --git a/src/org/kar/archidata/model/User.java b/src/org/kar/archidata/model/User.java index 535ccd0..06f20f3 100644 --- a/src/org/kar/archidata/model/User.java +++ b/src/org/kar/archidata/model/User.java @@ -30,12 +30,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; @SQLTableName ("user") @SQLIfNotExists @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) -public class User { - @SQLAutoIncrement // Add AUTO_INCREMENT modifier - @SQLPrimaryKey // Create a PRIMARY KEY based on this field - @SQLNotNull - @SQLComment("Primary key of the base") - public Long id = null; +public class User extends GenericTable { @SQLLimitSize(128) public String login = null; diff --git a/src/org/kar/archidata/model/UserSmall.java b/src/org/kar/archidata/model/UserSmall.java deleted file mode 100644 index 91cb8e8..0000000 --- a/src/org/kar/archidata/model/UserSmall.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.kar.archidata.model; - -/* -CREATE TABLE `user` ( - `id` bigint NOT NULL COMMENT 'table ID' AUTO_INCREMENT PRIMARY KEY, - `login` varchar(128) COLLATE 'utf8_general_ci' NOT NULL COMMENT 'login of the user', - `password` varchar(128) COLLATE 'latin1_bin' NOT NULL COMMENT 'password of the user hashed (sha512)', - `email` varchar(512) COLLATE 'utf8_general_ci' NOT NULL COMMENT 'email of the user', - `emailValidate` bigint COMMENT 'date of the email validation', - `newEmail` varchar(512) COLLATE 'utf8_general_ci' COMMENT 'email of the user if he want to change', - `authorisationLevel` enum("REMOVED", "USER", "ADMIN") NOT NULL COMMENT 'user level of authorization' -) AUTO_INCREMENT=10; - - */ - - -import java.sql.ResultSet; -import java.sql.SQLException; - -public class UserSmall { - public long id; - public String login; - public String email; - public State authorisationLevel; - - public UserSmall() { - } - - public UserSmall(long id, String login, String email, State authorisationLevel) { - this.id = id; - this.login = login; - this.email = email; - this.authorisationLevel = authorisationLevel; - } - - public UserSmall(ResultSet rs) { - int iii = 1; - try { - this.id = rs.getLong(iii++); - this.login = rs.getString(iii++); - this.email = rs.getString(iii++); - this.authorisationLevel = State.valueOf(rs.getString(iii++)); - } catch (SQLException ex) { - ex.printStackTrace(); - } - } - /* - public void serialize(ResultSet rs) { - int iii = 1; - try { - this.id = rs.getLong(iii++); - this.login = rs.getString(iii++); - this.password = rs.getString(iii++); - this.email = rs.getString(iii++); - this.emailValidate = rs.getLong(iii++); - this.newEmail = rs.getString(iii++); - this.authorisationLevel = State.valueOf(rs.getString(iii++)); - } catch (SQLException ex) { - ex.printStackTrace(); - } - } - */ - - @Override - public String toString() { - return "UserSmall{" + - "id='" + id + '\'' + - ", login='" + login + '\'' + - ", email='" + email + '\'' + - ", authorisationLevel=" + authorisationLevel + - '}'; - } -} diff --git a/src/org/kar/archidata/util/DataTools.java b/src/org/kar/archidata/util/DataTools.java index 431fc9a..89e9887 100644 --- a/src/org/kar/archidata/util/DataTools.java +++ b/src/org/kar/archidata/util/DataTools.java @@ -73,43 +73,23 @@ public class DataTools { } public static Data getWithSha512(String sha512) { - System.out.println("find sha512 = " + sha512); - DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - String query = "SELECT `id`, `deleted`, `sha512`, `mime_type`, `size` FROM `data` WHERE `sha512` = ?"; - try { - PreparedStatement ps = entry.connection.prepareStatement(query); - ps.setString(1, sha512); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Data out = new Data(rs); - entry.disconnect(); - return out; - } - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - entry.disconnect(); - return null; - + try { + return SqlWrapper.getWhere(Data.class, "sha512", "=", sha512); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; } public static Data getWithId(long id) { - DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - String query = "SELECT `id`, `deleted`, `sha512`, `mime_type`, `size` FROM `data` WHERE `deleted` = false AND `id` = ?"; - try { - PreparedStatement ps = entry.connection.prepareStatement(query); - ps.setLong(1, id); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Data out = new Data(rs); - entry.disconnect(); - return out; - } - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - entry.disconnect(); - return null; + try { + return SqlWrapper.getWhere(Data.class, "deleted", "=", false, "id", "=", id); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; } public static Data createNewData(long tmpUID, String originalFileName, String sha512) throws IOException, SQLException { @@ -141,41 +121,20 @@ public class DataTools { } String tmpPath = getTmpFileInData(tmpUID); long fileSize = Files.size(Paths.get(tmpPath)); - DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - long uniqueSQLID = -1; + Data out = new Data();; try { - // prepare the request: - String query = "INSERT INTO `data` (`sha512`, `mime_type`, `size`, `original_name`) VALUES (?, ?, ?, ?)"; - PreparedStatement ps = entry.connection.prepareStatement(query, - Statement.RETURN_GENERATED_KEYS); - int iii = 1; - ps.setString(iii++, sha512); - ps.setString(iii++, mimeType); - ps.setLong(iii++, fileSize); - ps.setString(iii++, originalFileName); - // execute the request - int affectedRows = ps.executeUpdate(); - if (affectedRows == 0) { - throw new SQLException("Creating data failed, no rows affected."); - } - // retreive 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 (Exception ex) { - System.out.println("Can not get the UID key inserted ... "); - ex.printStackTrace(); - throw new SQLException("Creating user failed, no ID obtained (2)."); - } + out.sha512 = sha512; + out.mimeType = mimeType; + out.size = fileSize; + out = SqlWrapper.insert(out); } catch (SQLException ex) { ex.printStackTrace(); - } - entry.disconnect(); - System.out.println("Add Data raw done. uid data=" + uniqueSQLID); - Data out = getWithId(uniqueSQLID); + return null; + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } String mediaPath = getFileData(out.id); System.out.println("src = " + tmpPath); @@ -183,22 +142,17 @@ public class DataTools { Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE); System.out.println("Move done"); - // all is done the file is corectly installed... - + // all is done the file is correctly installed... return out; } public static void undelete(Long id) { - DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - String query = "UPDATE `data` SET `deleted` = false WHERE `id` = ?"; - try { - PreparedStatement ps = entry.connection.prepareStatement(query); - ps.setLong(1, id); - ps.execute(); - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - entry.disconnect(); + try { + SqlWrapper.unsetDelete(Data.class, id); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } public static String saveTemporaryFile(InputStream uploadedInputStream, long idData) {