Threading Scheduling Java

Oggi ci approcceremo alla programmazione concorrente su di una web application Java basata sulla servlet.
Il threading scheduling Java può essere utile per operazioni particolarmente onerose che richiedono l’impiego di varie risorse dislocate nella nostra applicazione.
Talvolta essendo ormai le applicazioni tutte basate su AJAX, potrebbe non bastare una singola chiamata AJAX per avere un risultato perciò occorre schedulare queste operazioni in questo modo occorre che lo strato di frontend effettui un pool di chiamate per capire quando è terminata l’operazione onerosa lanciata in precedenza.
Vi faccio un esempio la generazione di un PDF particolarmente grande con immagini ecc..potrebbe impiegare dei minuti come faccio a bloccare la vista con un loader fintanto che non è stata ultimata la corretta creazione del PDF, con uno thread per l’appunto.

Per realizzare un pool di risorse in genere utilizzo questa metodologia che comprende tre livelli di mapping in una servlet. Per semplicità supponiamo che tutte queste chiamate siano in modalità GET, partendo dall’esempio di generare un PDF che impieghi svariato tempo computazionale in fase di generazione.

Per completezza per presentare questa problematica anche lato frontend supponiamo di mappare le operazioni sulle seguenti url:

  • Creazione PDF -> /create
  • Scheduling PDF -> /schedule
  • Download PDF Generato -> /download

Analizziamo la parte backend del Threading Scheduling Java.

Threading in Java

Threading Scheduling Java Cycle.

1. Creazione del PDF con lo ScheduledExecutorService -> /create

Questo metodo dopo aver recuperato eventuali parametri dalla GET, chiama un metodo statico dentro una classe definita per l’apposito scheduling.
Questa classe ha inoltre una mappa che memorizza una chiave con uuid ramdom univoco che identifica il processo di scheduling lanciato a cui associamo il File vero e proprio non appena termina la generazione del PDF. Ecco la classe in questione, con args indico parametri in generale.

public class PDFScheduling {
 
	protected final static Log logger  = LogFactory.getLog(PDFScheduling.class);
 
	public static Map<String, File> resultContinueMap = new HashMap<String, File>();
 
	public static String generatePdf(args...) throws JSONException, IOException {
 
		final String operationId = UUID.randomUUID().toString();
    	        logger.warn("QUEUED OPERATION " + operationId);
    	        resultContinueMap.put(operationId, null);
    	        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
    	        ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(new PDFRunnable(args...), 15, TimeUnit.SECONDS);
    	        scheduledExecutorService.shutdown();
                return operationId;
	}
}

Come vedete questo metodo inserisce nella mappa in continuo di cui parlavamo prima l’uuid univoco e null per il File poi lancerà lo ScheduledExecutorService che si occupa di gestire il thread, ma non solo nel metodo schedule troviamo anche il new di un oggetto PDFRunnable che implementa l’oggetto Runnable oltre al timeout di 15 secondi prima di lanciare lo scheduler.
Ecco come sarà costruita questa classe PDFRunnable, il cui metodo run possiede il codice concorrente.
Inoltre notate che le ultime righe del codice concorrente sono l’inserimento del file generato nella mappa in continuo.

 
public class PDFRunnable implements Runnable{
 
	private String arg1;
        .
        .
	private String argn;
	private String uuidProcess;
 
	protected final static Log logger  = LogFactory.getLog(PDFRunnable.class);
 
	public PDFRunnable(arg1...argn, String uuid) throws IOException{
    	      this.arg1 = arg1;
              .
              .
    	      this.argn = argn;
    	      this.uuidProcess = uuid;
        }
 
	@Override
	public void run() {
             //TO-DO Codice concorrente....
             File result = null;
             ....
             ....
	     PDFScheduling.resultContinueMap.put(this.uuidProcess, f);
	}
 
}

Per ulteriori dettagli su questa metodologia di threading scheduling Java è possibile consultare questi due links:

2. Scheduling del PDF -> /schedule
Nella servlet che chiama il metodo statico generatePdf realizziamo il metodo che gestisce il pooling, questa chiamata in GET chiama a sua volta un metodo statico che inseriremo per comodità nella classe PDFScheduling che restituirà semplicemente un booleano che restituisce true se e solo se per quello specifico UUID nella mappa in continuo è stato messo il file relativo al PDF generato, ecco come sarà l’implementazione di questo metodo.

	public static boolean isCompleted(String operationId) {
		if (resultContinueMap.containsKey(operationId) && resultContinueMap.get(operationId) == null) {
			return false;
		}
		return true;
	}

Mentre l’implemetazione della GET può essere simile a quanto sotto riportato:

        private void getOperationStatus(HttpServletRequest request, HttpServletResponse response, String uuid){
		JSONObject result = new JSONObject();
		try {
			boolean isCompleted = PDFScheduling.isCompleted(uuid);
			if(isCompleted)
				result.put("status", "complete");
			else
				result.put("status", "locked");
 
			result.put("processedUuid", uuid);
			Utils.doPrint(result, response);
		} catch (JSONException | DaoException e) {
			try {
				Utils.doError(result, response, e);
			} catch (IOException e1) {
				PDFServlet.logger.error(e1, e1);
				response.setStatus(501);
			}
		}
	}

3. Download PDF Generato -> /download
Si riporta infine per concludere lo strato di backend il download del pdf che si supponiamo sia stato archiviato sul file system del web server.
Tale metodo risulta essere abbastanza intuitivo.

         private void downloadPDF(HttpServletRequest request, HttpServletResponse response, String uuid, String filename){
		OutputStream responseOutputStream = null;
		FileInputStream fileInputStream = null;
		File fileDownload = null;
		try{
			response.setContentType("application/force-download");
	                response.setHeader("Content-Disposition", "attachment; filename=\""+filename+".pdf\"");
 
	                fileDownload = PDFScheduling.resultContinueMap.get(uuid);
 
	                fileInputStream = new FileInputStream(fileDownload);
	                PDFScheduling.resultContinueMap.remove(uuid);
			responseOutputStream = response.getOutputStream();
			int bytes;
			while ((bytes = fileInputStream.read()) != -1) {
				responseOutputStream.write(bytes);
			}
		} catch (Exception e) {
			DownloadServlet.logger.error("Generic error during build PDF", e);
			response.setHeader("X-Generator", "failed");
		}finally{
			try {
				response.flushBuffer();
			} catch (IOException e1) {
				PDFServlet.logger.error(e1,e1);
			}
			if(fileInputStream!=null){
				try {
					fileInputStream.close();
				} catch (IOException e) {
					PDFServlet.logger.error(e, e);
				}
			}
			if(fileDownload!=null){
				fileDownload.delete();
			}
			if(responseOutputStream!=null){
				try {
					responseOutputStream.close();
					responseOutputStream.flush();
				} catch (IOException e) {
					PDFServlet.logger.error(e, e);
				}
			}
		}
	}

Come vedete sia nel metodo di scheduling sia nel download abbiamo l’UUID che inizialmente viene restituito dal metodo generatePdf nella prima chiamata di creazione del PDF al frontend in questo modo diciamo che è partito un pool.
Quale è l’idea? Dopo che abbiamo questo UUID che identifica il processo schedulato invochiamo infinite volte la chiamata AJAX mappata su /schedule fino a che essa non restituisce true. Non appena essa restituisce true si può invocare l’effettivo download del PDF 🙂 Ecco come possiamo strutturare questa idea mediante chiamate AJAX.

Chiamerò per semplicità blockUI() e unBlockUI() i metodi che supponiamo inseriscano in loader che blocca lo strato di presentation con un loader.

var _pdfGeneratorManager = function() {
 
        /* 1. Create PDF -> /create */
	var downloadPdf = function(){
		blockUI();
		$.get(".../servlet/create", function(data, textStatus, xhr) {
			if(data && data.success == true && data.processedUuid != undefined){
				console.log(data);
				doPoll(data.processedUuid);
			}
	    })
	    .fail(function (data) {
	    	console.log(data);
	    }());
	}
 
        /* 2. Schedule PDF -> /schedule */
	var doPoll = function(uid){
	    $.get('.../servlet/schedule?uuid='+uid, function(data, textStatus, xhr) {
	    	if(data && data.processedUuid && data.status == "complete"){
	    		downloadPDF(data.processedUuid);
	    	}else {
	    		setTimeout(function() {doPoll(uid)},1000);
	    	}
	    })
	    .fail(function (uid) {
	    	return function(jqXHR, textStatus, errorThrown ) {
	    		if (jqXHR.status == 202) {
	    			setTimeout(function() {doPoll(uid)},1000);
	        	} 
	    	}
	    }(uid));
	}
 
        /* 3. Download PDF -> /download */
	var downloadPDF = function(uid) {
		var aLink = document.createElement("a");
		aLink.href = ".../servlet/downloadPdf?uuid="+uid;
		window.location.href = aLink.href;
		unBlockUI();
	}
 
}
 
var PDFGeneratorManager = new _pdfGeneratorManager();

Come vedete i medesimi tre passaggi visti per il back-end sono stati inseriti qua in JavaScript per realizzare lo scheduler, infatti in breve prima della prima chiamata viene bloccata la vista con blockUI(), questa chiamata AJAX onerosa restituisce un JSON con l’uuid del processo che è partito e che p in fase di lavorazione.
Nel success di questa $.get chiamo il metodo doPoll che viene chiamato ogni 1000ms, nel momento in cui il JSON della seconda chiamata (data.status) ritorna true (potete vedere sopra il metodo Java getOperationStatus) allora parte l’effettivo download del PDF mediante il terzo metodo JavaScript.

Abbiamo visto un semplice metodo di programmazione concorrente che solo a pensarci sembra essere difficile ma approcciandosi in questa maniera abbiamo una piccola base di partenza per costruire applicazione multi-threading molto più complessa.

Threading Scheduling Java ultima modidfica: 2017-12-09T16:30:04+01:00 da Gianluca Di Vincenzo