mirror of
				https://github.com/simon987/music-graph-api.git
				synced 2025-10-30 19:06:52 +00:00 
			
		
		
		
	Chart builder setup, release details endpoint
This commit is contained in:
		
							parent
							
								
									606a95279e
								
							
						
					
					
						commit
						98aae25a72
					
				
							
								
								
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							| @ -122,11 +122,5 @@ | ||||
|             <artifactId>sqlite-jdbc</artifactId> | ||||
|             <version>3.27.2.1</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.jetbrains</groupId> | ||||
|             <artifactId>annotations</artifactId> | ||||
|             <version>17.0.0</version> | ||||
|             <scope>compile</scope> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| </project> | ||||
| @ -2,9 +2,7 @@ package net.simon987.musicgraph; | ||||
| 
 | ||||
| import net.simon987.musicgraph.io.*; | ||||
| import net.simon987.musicgraph.logging.LogManager; | ||||
| import net.simon987.musicgraph.webapi.ArtistController; | ||||
| import net.simon987.musicgraph.webapi.CoverController; | ||||
| import net.simon987.musicgraph.webapi.Index; | ||||
| import net.simon987.musicgraph.webapi.*; | ||||
| import org.glassfish.grizzly.http.server.HttpServer; | ||||
| import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; | ||||
| import org.glassfish.jersey.jackson.JacksonFeature; | ||||
| @ -18,7 +16,6 @@ public class Main { | ||||
|     private final static Logger logger = LogManager.getLogger(); | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
| 
 | ||||
|         startHttpServer(); | ||||
|     } | ||||
| 
 | ||||
| @ -28,12 +25,16 @@ public class Main { | ||||
| 
 | ||||
|         rc.registerInstances(new MusicDatabase()); | ||||
|         rc.registerInstances(new SQLiteCoverArtDatabase("covers.db")); | ||||
|         rc.registerInstances(new MagickChartBuilder("/dev/shm/im_chart/")); | ||||
| 
 | ||||
|         rc.registerClasses(Index.class); | ||||
|         rc.registerClasses(ArtistController.class); | ||||
|         rc.registerClasses(CoverController.class); | ||||
|         rc.registerClasses(ChartController.class); | ||||
|         rc.registerClasses(JacksonFeature.class); | ||||
| 
 | ||||
|         rc.registerClasses(MyExceptionMapper.class); | ||||
| 
 | ||||
|         try { | ||||
|             HttpServer server = GrizzlyHttpServerFactory.createHttpServer( | ||||
|                     new URI("http://localhost:3030/"), | ||||
|  | ||||
| @ -0,0 +1,17 @@ | ||||
| package net.simon987.musicgraph.entities; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class ReleaseDetails { | ||||
| 
 | ||||
|     public String name; | ||||
|     public String mbid; | ||||
|     public String artist; | ||||
|     public long year; | ||||
|     public List<Tag> tags; | ||||
| 
 | ||||
|     public ReleaseDetails() { | ||||
|         this.tags = new ArrayList<>(); | ||||
|     } | ||||
| } | ||||
| @ -1,5 +1,8 @@ | ||||
| package net.simon987.musicgraph.io; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public interface ICoverArtDatabase { | ||||
| 
 | ||||
|     /** | ||||
| @ -8,4 +11,6 @@ public interface ICoverArtDatabase { | ||||
|      * @throws Exception if unexpected error | ||||
|      */ | ||||
|     byte[] getCover(String mbid) throws Exception; | ||||
| 
 | ||||
|     HashMap<String, byte[]> getCovers(List<String> mbids) throws Exception; | ||||
| } | ||||
|  | ||||
| @ -155,4 +155,49 @@ public class MusicDatabase extends AbstractBinder { | ||||
|         return relation; | ||||
|     } | ||||
| 
 | ||||
|     public ReleaseDetails getReleaseDetails(String mbid) { | ||||
| 
 | ||||
|         Map<String, Object> params = new HashMap<>(); | ||||
|         params.put("mbid", mbid); | ||||
| 
 | ||||
|         try (Session session = driver.session()) { | ||||
| 
 | ||||
|             StatementResult result = query(session, | ||||
|                     "MATCH (release:Release {id: $mbid})-[:CREDITED_FOR]-(a:Artist)\n" + | ||||
|                             "OPTIONAL MATCH (release)-[r:IS_TAGGED]->(t:Tag)\n" + | ||||
|                             "RETURN release {name:release.name, year:release.year," + | ||||
|                             "tags:collect({weight:r.weight, name:t.name}), artist: a.name}\n" + | ||||
|                             "LIMIT 1", | ||||
|                     params); | ||||
| 
 | ||||
|             ReleaseDetails details = new ReleaseDetails(); | ||||
| 
 | ||||
|             try { | ||||
| 
 | ||||
|                 if (result.hasNext()) { | ||||
|                     Map<String, Object> map = result.next().get("release").asMap(); | ||||
| 
 | ||||
|                     details.mbid = mbid; | ||||
|                     details.name = (String) map.get("name"); | ||||
|                     details.artist = (String) map.get("artist"); | ||||
|                     details.year = (long) map.get("year"); | ||||
|                     details.tags.addAll( | ||||
|                             ((List<Map>) map.get("tags")) | ||||
|                                     .stream() | ||||
|                                     .filter(x -> x.get("name") != null) | ||||
|                                     .map(x -> new Tag( | ||||
|                                             (String) x.get("name"), | ||||
|                                             (Long) x.get("weight") | ||||
|                                     )) | ||||
|                                     .collect(Collectors.toList()) | ||||
|                     ); | ||||
|                 } | ||||
|             } catch (Exception e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
| 
 | ||||
|             return details; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,8 @@ import net.simon987.musicgraph.logging.LogManager; | ||||
| import org.glassfish.jersey.internal.inject.AbstractBinder; | ||||
| 
 | ||||
| import java.sql.*; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.logging.Logger; | ||||
| 
 | ||||
| public class SQLiteCoverArtDatabase extends AbstractBinder implements ICoverArtDatabase { | ||||
| @ -47,6 +49,33 @@ public class SQLiteCoverArtDatabase extends AbstractBinder implements ICoverArtD | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public HashMap<String, byte[]> getCovers(List<String> mbids) throws SQLException { | ||||
| 
 | ||||
|         HashMap<String, byte[]> covers = new HashMap<>(mbids.size()); | ||||
| 
 | ||||
|         try { | ||||
|             setupConn(); | ||||
| 
 | ||||
|             PreparedStatement stmt = connection.prepareStatement( | ||||
|                     //TODO: sqlite injection | ||||
|                     String.format("SELECT id, cover FROM covers WHERE id IN ('%s')", | ||||
|                             String.join("','", mbids)) | ||||
|             ); | ||||
|             ResultSet rs = stmt.executeQuery(); | ||||
| 
 | ||||
|             while (rs.next()) { | ||||
|                 covers.put(rs.getString(1), rs.getBytes(2)); | ||||
|             } | ||||
| 
 | ||||
|             return covers; | ||||
| 
 | ||||
|         } catch (SQLException e) { | ||||
|             logger.severe(String.format("Exception during cover art query mbid=%s ex=%s", | ||||
|                     mbids, e.getMessage())); | ||||
|             throw e; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void setupConn() throws SQLException { | ||||
|         try { | ||||
|             if (connection == null || connection.isClosed()) { | ||||
|  | ||||
| @ -9,7 +9,7 @@ import java.util.logging.LogRecord; | ||||
| 
 | ||||
| public class MusicGraphFormatter extends Formatter { | ||||
| 
 | ||||
|     private static final DateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss"); | ||||
|     private static final DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); | ||||
| 
 | ||||
|     @Override | ||||
|     public String format(LogRecord record) { | ||||
|  | ||||
| @ -0,0 +1,48 @@ | ||||
| package net.simon987.musicgraph.webapi; | ||||
| 
 | ||||
| import net.simon987.musicgraph.io.ICoverArtDatabase; | ||||
| import net.simon987.musicgraph.io.MusicDatabase; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.ws.rs.GET; | ||||
| import javax.ws.rs.NotFoundException; | ||||
| import javax.ws.rs.Path; | ||||
| import javax.ws.rs.Produces; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| @Path("/chart") | ||||
| public class ChartController { | ||||
| 
 | ||||
|     @Inject | ||||
|     IChartBuilder chartBuilder; | ||||
| 
 | ||||
|     @Inject | ||||
|     ICoverArtDatabase coverArtDatabase; | ||||
| 
 | ||||
|     @Inject | ||||
|     private MusicDatabase musicDatabase; | ||||
| 
 | ||||
|     @GET | ||||
|     @Produces("image/png") | ||||
|     public byte[] makeChart() throws Exception { | ||||
|         var opt = new ChartOptions(); | ||||
| 
 | ||||
|         throw new NotFoundException(); | ||||
| 
 | ||||
| //        List<String> releases = new ArrayList<>(); | ||||
| // | ||||
| //        opt.cols = 4; | ||||
| //        opt.rows = 5; | ||||
| //        opt.covers = coverArtDatabase.getCovers(releases); | ||||
| //        opt.backgroundColor = "blue"; | ||||
| //        opt.details = releases | ||||
| //                .parallelStream() // Thread-safe? | ||||
| //                .map(r -> musicDatabase.getReleaseDetails(r)) | ||||
| //                .collect(Collectors.toList()); | ||||
| // | ||||
| //        return chartBuilder.makeChart(opt); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package net.simon987.musicgraph.webapi; | ||||
| 
 | ||||
| import net.simon987.musicgraph.entities.ReleaseDetails; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class ChartOptions { | ||||
| 
 | ||||
|     public HashMap<String, byte[]> covers; | ||||
|     public List<ReleaseDetails> details; | ||||
|     public int rows; | ||||
|     public int cols; | ||||
|     public String backgroundColor; | ||||
|     public String textColor = "white"; | ||||
|     public String font = "Hack-Regular"; | ||||
|     public String borderColor = "white"; | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| package net.simon987.musicgraph.webapi; | ||||
| 
 | ||||
| public interface IChartBuilder { | ||||
| 
 | ||||
|     byte[] makeChart(ChartOptions options) throws Exception; | ||||
| } | ||||
| @ -0,0 +1,306 @@ | ||||
| package net.simon987.musicgraph.webapi; | ||||
| 
 | ||||
| import net.simon987.musicgraph.entities.ReleaseDetails; | ||||
| import org.glassfish.jersey.internal.inject.AbstractBinder; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| public class MagickChartBuilder extends AbstractBinder implements IChartBuilder { | ||||
| 
 | ||||
|     private String workspacePath; | ||||
|     private File workspace; | ||||
| 
 | ||||
|     private static final String[] letters = new String[]{ | ||||
|             "A", "B", "C", "D", "E", "F", "G", "H", "I" | ||||
|     }; | ||||
| 
 | ||||
|     @Override | ||||
|     protected void configure() { | ||||
|         bind(this).to(IChartBuilder.class); | ||||
|     } | ||||
| 
 | ||||
|     public MagickChartBuilder(String workspacePath) { | ||||
|         this.workspacePath = workspacePath; | ||||
|         this.workspace = new File(workspacePath); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public byte[] makeChart(ChartOptions options) throws Exception { | ||||
| 
 | ||||
|         ProcessBuilder processBuilder = new ProcessBuilder(); | ||||
|         List<String> releases = options.details.stream().map(o -> o.mbid).collect(Collectors.toList()); | ||||
| 
 | ||||
|         File chartWorkspace = makeChartWorkspace(); | ||||
| 
 | ||||
|         saveCoversToDisk(options, chartWorkspace); | ||||
|         makeGrid(options, chartWorkspace, releases); | ||||
|         File header = makeHeader(options, chartWorkspace); | ||||
|         File side = makeSide(options, chartWorkspace); | ||||
| 
 | ||||
|         // Side + grid | ||||
|         processBuilder | ||||
|                 .directory(chartWorkspace) | ||||
|                 .command( | ||||
|                         "convert", | ||||
|                         "+append", | ||||
|                         side.toString(), | ||||
|                         "grid.png", | ||||
|                         "grid_with_side.png" | ||||
|                 ) | ||||
|                 .start() | ||||
|                 .waitFor(); | ||||
| 
 | ||||
|         // Side + grid | ||||
|         processBuilder | ||||
|                 .directory(chartWorkspace) | ||||
|                 .command( | ||||
|                         "convert", | ||||
|                         "-append", | ||||
|                         "-gravity", "east", | ||||
|                         header.toString(), | ||||
|                         "grid_with_side.png", | ||||
|                         "final_grid.png" | ||||
|                 ) | ||||
|                 .start() | ||||
|                 .waitFor(); | ||||
| 
 | ||||
|         makeDescription(options, chartWorkspace); | ||||
| 
 | ||||
|         // + description | ||||
|         processBuilder | ||||
|                 .directory(chartWorkspace) | ||||
|                 .command( | ||||
|                         "convert", | ||||
|                         "+append", | ||||
|                         "-gravity", "south", | ||||
|                         "final_grid.png", | ||||
|                         "description.png", | ||||
|                         "final.png" | ||||
|                 ) | ||||
|                 .start() | ||||
|                 .waitFor(); | ||||
| 
 | ||||
| 
 | ||||
|         FileInputStream file = new FileInputStream(new File(chartWorkspace, "final.png")); | ||||
|         byte[] chart = file.readAllBytes(); | ||||
|         file.close(); | ||||
| 
 | ||||
|         cleanup(chartWorkspace); | ||||
| 
 | ||||
|         return chart; | ||||
|     } | ||||
| 
 | ||||
|     private void cleanup(File chartWorkspace) { | ||||
|         String[] entries = chartWorkspace.list(); | ||||
|         if (entries != null) { | ||||
|             for (String s : entries) { | ||||
|                 File currentFile = new File(chartWorkspace, s); | ||||
|                 currentFile.delete(); | ||||
|             } | ||||
|         } | ||||
|         chartWorkspace.delete(); | ||||
|     } | ||||
| 
 | ||||
|     private File makeChartWorkspace() throws IOException { | ||||
|         String chartId = UUID.randomUUID().toString(); | ||||
| 
 | ||||
|         File chartWorkspace = new File(workspacePath, chartId); | ||||
|         if (!chartWorkspace.mkdir()) { | ||||
|             throw new IOException(String.format("Could not create chart workspace: %s", chartWorkspace.getAbsolutePath())); | ||||
|         } | ||||
|         return chartWorkspace; | ||||
|     } | ||||
| 
 | ||||
|     private void saveCoversToDisk(ChartOptions options, File chartWorkspace) throws IOException { | ||||
|         for (ReleaseDetails releaseDetails : options.details) { | ||||
|             if (options.covers.containsKey(releaseDetails.mbid)) { | ||||
|                 FileOutputStream file = new FileOutputStream(new File(chartWorkspace, releaseDetails.mbid)); | ||||
|                 file.write(options.covers.get(releaseDetails.mbid)); | ||||
|                 file.close(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void makeDescription(ChartOptions options, File chartWorkspace) | ||||
|             throws InterruptedException, IOException { | ||||
| 
 | ||||
|         ProcessBuilder processBuilder = new ProcessBuilder(); | ||||
| 
 | ||||
|         for (int i = 0; i < options.rows; i++) { | ||||
| 
 | ||||
|             String desc = formatDescription(options, i); | ||||
| 
 | ||||
|             processBuilder | ||||
|                     .directory(chartWorkspace) | ||||
|                     .command( | ||||
|                             "convert", | ||||
|                             "-size", "620x316", | ||||
|                             "xc:" + options.backgroundColor, | ||||
|                             "-fill", options.textColor, | ||||
|                             "-pointsize", "20", | ||||
|                             "-font", options.font, | ||||
|                             "-annotate", "+0+40", desc, | ||||
|                             String.format("d_%d.png", i) | ||||
|                     ) | ||||
|                     .start() | ||||
|                     .waitFor(); | ||||
|             processBuilder | ||||
|                     .directory(chartWorkspace) | ||||
|                     .command( | ||||
|                             "convert", | ||||
|                             "-append", | ||||
|                             "d_*.png", | ||||
|                             "description.png" | ||||
|                     ) | ||||
|                     .start() | ||||
|                     .waitFor(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static String formatDescription(ChartOptions options, int row) { | ||||
| 
 | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
| 
 | ||||
|         for (int col = 0; col < options.cols; col++) { | ||||
| 
 | ||||
|             int releaseIndex = row * options.cols + col; | ||||
|             if (releaseIndex >= options.details.size()) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             ReleaseDetails release = options.details.get(releaseIndex); | ||||
| 
 | ||||
|             String year = release.year != 0 ? String.format("(%s)", release.year) : ""; | ||||
| 
 | ||||
|             if (release.name.length() + release.artist.length() < 40) { | ||||
|                 sb.append(String.format("%s%d %s - %s %s\n", | ||||
|                         letters[row], col+1, | ||||
|                         release.name, release.artist, year)); | ||||
|             } else { | ||||
|                 sb.append(String.format("%s%d %s -\n\t\t\t %s %s\n", | ||||
|                         letters[row], col+1, | ||||
|                         release.name, release.artist, year)); | ||||
|             } | ||||
|         } | ||||
|         return sb.toString(); | ||||
|     } | ||||
| 
 | ||||
|     private void makeGrid(ChartOptions options, File chartWorkspace, List<String> releases) | ||||
|             throws InterruptedException, IOException { | ||||
| 
 | ||||
|         ProcessBuilder processBuilder = new ProcessBuilder(); | ||||
| 
 | ||||
|         List<String> cmd = new ArrayList<>(); | ||||
|         cmd.add("montage"); | ||||
|         cmd.addAll(releases); | ||||
|         cmd.add("-tile"); | ||||
|         cmd.add(String.format("%dx%d", options.cols, options.rows)); | ||||
|         cmd.add("-background"); | ||||
|         cmd.add(options.backgroundColor); | ||||
|         cmd.add("-border"); | ||||
|         cmd.add("1"); | ||||
|         cmd.add("-bordercolor"); | ||||
|         cmd.add(options.borderColor); | ||||
|         cmd.add("-geometry"); | ||||
|         cmd.add("256x256+30+30"); | ||||
|         cmd.add("grid.png"); | ||||
| 
 | ||||
|         processBuilder | ||||
|                 .directory(chartWorkspace) | ||||
|                 .command(cmd) | ||||
|                 .start() | ||||
|                 .waitFor(); | ||||
|     } | ||||
| 
 | ||||
|     private File makeSide(ChartOptions options, File chartWorkSpace) | ||||
|             throws InterruptedException, IOException { | ||||
| 
 | ||||
|         File sideFile = new File(chartWorkSpace, "side.png"); | ||||
| 
 | ||||
|         ProcessBuilder processBuilder = new ProcessBuilder(); | ||||
| 
 | ||||
|         for (int i = 0; i < options.rows; i++) { | ||||
|             processBuilder | ||||
|                     .directory(chartWorkSpace) | ||||
|                     .command( | ||||
|                             "convert", | ||||
|                             "-background", options.backgroundColor, | ||||
|                             "-gravity", "east", | ||||
|                             "-size", "60x256", | ||||
|                             "-fill", options.textColor, | ||||
|                             "-pointsize", "64", | ||||
|                             "-font", options.font, | ||||
|                             "-strip", | ||||
|                             String.format("label:%s", letters[i]), | ||||
|                             String.format("s_%d.png", i + 1) | ||||
|                     ) | ||||
|                     .start() | ||||
|                     .waitFor(); | ||||
|         } | ||||
| 
 | ||||
|         processBuilder | ||||
|                 .directory(chartWorkSpace) | ||||
|                 .command( | ||||
|                         "montage", | ||||
|                         "-tile", "1x", | ||||
|                         "-background", options.backgroundColor, | ||||
|                         "-geometry", "+0+31", | ||||
|                         "s_*.png", | ||||
|                         sideFile.toString() | ||||
|                 ) | ||||
|                 .start() | ||||
|                 .waitFor(); | ||||
| 
 | ||||
|         return sideFile; | ||||
|     } | ||||
| 
 | ||||
|     private File makeHeader(ChartOptions options, File chartWorkspace) | ||||
|             throws InterruptedException, IOException { | ||||
| 
 | ||||
|         File headerFile = new File(chartWorkspace, "header.png"); | ||||
|         ProcessBuilder processBuilder = new ProcessBuilder(); | ||||
| 
 | ||||
|         // Header | ||||
|         for (int i = 1; i <= options.cols; i++) { | ||||
|             processBuilder | ||||
|                     .directory(chartWorkspace) | ||||
|                     .command( | ||||
|                             "convert", | ||||
|                             "-background", options.backgroundColor, | ||||
|                             "-gravity", "south", | ||||
|                             "-size", "256x80", | ||||
|                             "-fill", options.textColor, | ||||
|                             "-chop", "0x12", | ||||
|                             "-pointsize", "64", | ||||
|                             "-font", options.font, | ||||
|                             "-strip", | ||||
|                             String.format("label:%d", i), | ||||
|                             String.format("h_%d.png", i) | ||||
|                     ) | ||||
|                     .start() | ||||
|                     .waitFor(); | ||||
|         } | ||||
| 
 | ||||
|         processBuilder | ||||
|                 .directory(chartWorkspace) | ||||
|                 .command( | ||||
|                         "montage", | ||||
|                         "-tile", "x1", | ||||
|                         "h_*.png", | ||||
|                         "-background", options.backgroundColor, | ||||
|                         "-geometry", "+31+0", | ||||
|                         headerFile.toString() | ||||
|                 ) | ||||
|                 .start() | ||||
|                 .waitFor(); | ||||
| 
 | ||||
|         return headerFile; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| package net.simon987.musicgraph.webapi; | ||||
| 
 | ||||
| import javax.ws.rs.core.Response; | ||||
| import javax.ws.rs.ext.ExceptionMapper; | ||||
| import javax.ws.rs.ext.Provider; | ||||
| 
 | ||||
| import org.glassfish.grizzly.utils.Exceptions; | ||||
| 
 | ||||
| @Provider | ||||
| public class MyExceptionMapper implements ExceptionMapper<Throwable> { | ||||
|     @Override | ||||
|     public Response toResponse(Throwable ex) { | ||||
| 
 | ||||
|         //TODO: logger | ||||
| 
 | ||||
|         return Response.status(500) | ||||
|                 .entity(Exceptions.getStackTraceAsString(ex)) | ||||
|                 .type("text/plain") | ||||
|                 .build(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user