Benutzer-Werkzeuge

Webseiten-Werkzeuge


prog:oracle_apex_jasper_reports

Oracle APEX 5 und Jasper Reports 6 zusammen installieren und nützen

Aus HTML Anwendungen ist das Drucken meist eine Herausforderung, hier ist leider auch APEX keine Ausnahme.

Eine Möglichkeit ist das Erstellen eines Berichts als PDF, der dann ausgedruckt werden kann.

Ein hervorragendes, leider aber auch ein etwas arg kostenpflichtiges, Tool ist das zu der Oracle BI Publisher ⇒ Reporting mit der Oracle Datenbank.

Steht dieser nicht zur Verfügung ist es eine Alternative ein Open Source Produkt wie Jasper Report einzusetzen.

Eine sehr gute Anleitung + Software dazu bzgl. einer tiefen Apex Integration findet sich hier ⇒ http://www.opal-consulting.de/apex/f?p=20090928:7:0::NO:7:: .

Im ersten Schritt versuche ich das aber mal mit dem derzeitigen APEX 5 und PL/SQL in der 12c Board Mitteln zu lösen.


Aufsetzen einer Jasper Reports Umgebung

Voraussetzung:

Eine Umgebung mit Tomcat und den ORDS für Oracle Apex ist bereits installiert, siehe ⇒ Oracle ORDS 3.0 (Oracle REST Data Services) mit dem Apache http und Tomcat unter Windows 2012 betreiben - SSL verwenden und eine Oracle Datenbank steht zur Verfügung.

Leider lässt sich adhoc das Repository von Jasper NICHT in der Oracle Datenbank anlegen.

Auf diese existierenden Umgebung wird nun eine Community Jasper Report 6.2 Applikation aufgesetzt.

Download der Software über http://community.jaspersoft.com/download

  • JasperReports® Server ⇒ jasperreports-server-cp-6.2.1-bin.zip und jasperreports-server-cp-6.2.1-windows-x64-installer
  • JasperReports Library ⇒ jasperreports-6.2.2.jar
  • Jaspersoft® Studio ⇒ TIBCOJaspersoftStudio-6.2.2.final-windows-x86_64.zip

Dazu ist einen Community Mitgliedschaft notwendig.

Standalone Jasper Umgebung

Ziel ist es das alles unter Oracle läuft, leider wird das aber nativ nicht unterstütz .

Daher im ersten Schritt auf einer Testmaschine über den Windows Installer „jasperreports-server-cp-6.2.1-windows-x64-installer.exe“ eine erste Version des Report Servers mit einer Postgresql DB als Repository aufgesetzt.

  • Exe starten next - next
  • Lizenzbedingungen akzeptieren - next
  • Custom install auswählen - next
  • Programm Verzeichnis auswählen wie d:\srv\jasper6
  • „I want to use an existing Tomcat“ auswählen - next
  • „I want to use the bundled PostgreSQL database“ auswählen - next
  • Tomcat Server Port angeben,in unsere Fall 8081 - next
    • Wenn Tomcat noch läuft diesen erst stoppen? hilft nicht daher jetzt oben 8081 angeben Bug?
    • Port muss unterschiedlich sein, ist aber ein Bug im Installer!
  • Database Server Port angeben 5432 - next
  • Tomcat Verzeichnis auswählen - in unseren Fall D:\srv\apache-tomcat-8.0.33 - next
  • Samples mit installieren - Next
  • Installation starten mit Next
  • Installation läuft ca. 5min
  • Finish

Nun den Tomcat wieder starten, Jasper wird deployed, leider klappt es aber gar nicht ….

Jetzt heißt es den Fehler suchen….

09-Jun-2016 22:25:10.943 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'themeSource' defined in ServletContext resource [/WEB-INF/applicationContext-themes.xml]: Cannot resolve reference to bean 'themeCache' while setting bean property 'themeCache'; 

⇒> postgresql war nicht gestartet! Starten mit „D:\srv\jasper6\postgresql\scripts\servicerun.bat START“, falls das nicht funktioniert prüfen ob der Dienst installiert ist, fehlt dieser mit „serviceinstall.bat INSTALL“ installieren!

Siehe dazu auch zur Installation ⇒ http://community.jaspersoft.com/wiki/installation-steps-installer-distribution

Das wir in unserer Umgebung den Apache http Server mit mod_jk einsetzen ( und das ganze über SSL sichern), die entsprechenden Anweisung in die Conf Files einbauen, damit der Jasper Report aufgerufen werden kann.

apex.conf:

..
JKMount /jasperserver/* worker1
..

Login in den Jasper Report Server

Login mit dem User jasperadmin / pwd jasperadmin , diese SOFORT anpassen!

Local Test mit http://localhost:8080/jasperserver/login.html Produktiv aufrufen mit https://<myapex_server>/jasperserver/login.html

Nun noch eine Datasource für die Oracle Datenbank anlegen, unter „Data Source“ „Add Resource“ „Data Source“

Als erstes über „Select Driver“ den Oracle JDBC Treiber hochladen und dann die Connection zur DB konfigurieren.

Repository nach Oracle verschieben

Aus den Sourcen lässt sich das SQL für das Repository ermitteln, evtl. lässt sich daraus eine Oracle Version „basteln“.

Dazu die Datei „jasperreports-server-cp-6.2.1-bin.zip“ auf den Server kopieren und dort zum Beispiel nach „d:\install\jasper“ und dort entpacken. Unter „buildomatic\install_resources\sql“ finden sich die SQL Scripte für MySQL.

Diese müssen nun auf die Oracle Syntax angepasst werden.

Alles wird nun in der Oracle gleich wie in der Postgresql eingerichtet und dann wird die DB Connection umgezogen.

Mal sehen ob das so einfach dann klappt.

demnächst mehr


Report Designer starten

Die Datei „TIBCOJaspersoftStudio-6.2.2.final-windows-x86_64.zip“ entpacken und Ordner „jaspersoftstudio“ in ein Verzeichnis wie „D:\tools“ verschieben.

Über „Jaspersoft Studio.exe“ kann nun das angepasste Eclipse gestartet werden.

Als erstes wird ein Data Adapter benötigt, dazu muss aber zuvor der Pfad zu den JDBC Jars von Oracle definiert werden.

  •  Pfad zur JDBC Lib festlegen
  •  Verbindung einrichten

Nun kann über „File“ und der Auswahl eines Templates gleich der erste Bericht erzeugt werden, dabei darauf achten das der zuvor angelegte DataAdapter auch ausgewählt wird.

Nach dem ersten Report diesen auf den Server hochladen, dazu im Studio eine Server Verbindung anlegen und dort den Bericht hochladen.


Integration in Apex

Die einfachste Integration ist das Weiterleiten der URL an den Report Server von Jasper.

Allerdings ist dann das Ganze nicht direkt integriert, mit den Benutzerrechten und dem Passwort dazu ist das nicht so einfach.

Besser ist eine tiefe Integration und der Aufruf des Berichts über die REST Schnittstelle zum Jasper Server.

Dies kann nun per JavaScript in der Oberfläche erfolgen oder per UTL_HTTP über PL/SQL in der Datenbank.

Als erstes mit einem Rest Tool wie http://www.wiztools.org/ testen, wie die Rest Api von Jasper so funktioniert. Dazu restclient-ui-3.5-jar-with-dependencies.jar herunterladen und testen.

JavaScript

„Visualize.js currently does not support JasperReports Server Community Edition“

Visualize.js

APEX - PL/SQL

Die Integration in Apex erfolgt dadruch, das im Backend, d.h. in der Datenbank ein REST Call an den Jasper Server versandt wird, dies Call liefert den Bericht im gewünschten Format zurück, Diese Bericht wird dann in einer BLOB Variable gespeichert, der Inhalt dieser Variable wird dann an den Browser gestreamt.

APEX Jasper Integration

APEX_WEB_SERVICE verwenden

siehe https://docs.oracle.com/cloud/latest/db121/AEAPI/apex_web_service.htm#AEAPI537

Mit Hilfe von „apex_web_service.make_rest_request“ wird die API von Jasper Reports angesprochen.

Zuvor eine ACL hinterlegen, das wir überhaupt aus der DB auf den Report Server zugreifen dürfen.

ACL:

   DECLARE
      v_apex_server VARCHAR2(256):= '10.10.10.180';
    BEGIN
        DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
               host       => v_apex_server
            ,  lower_port => 80
            ,  upper_port => 80
            ,  ace        => xs$ace_type(privilege_list => xs$name_list('connect'),
                                         principal_name => 'apex_050000',
                                         principal_type => xs_acl.ptype_db)
        );
    END;
    /

Ein erster Test, der aber leider für diesen Einsatzzweck nicht erfolgreich ist, das Dokument wird als CLOB zurückgeben, das führt leider zu einer Zeichensatzwandlung und damit ist das Dokument hin:

DECLARE
   v_file           clob;
   v_blob           blob;
   v_rest_url       VARCHAR2(2000);
   v_server_url     VARCHAR2(300):='http://10.10.10.180/jasperserver/';
   -- use this to get resource infos for this report
   v_resoure_path   VARCHAR2(300):='rest_v2/resources/';
   -- get the report
   v_report_path    VARCHAR2(300):='rest_v2/reports/';
   -- Which Report   
   v_report_name    VARCHAR2(200):='Primavera/PrimaTest';
   -- 
   v_user           VARCHAR2(32):='gpi';
   v_pwd            VARCHAR2(32):='gpi';
   -- read the clob
   v_buffer         VARCHAR2(32767);
   v_buffer_raw     RAW(32000);
   v_amount         NUMBER;
   v_offset         NUMBER;
   v_file_length    PLS_INTEGER;
   -- set the data
   v_mime_type      VARCHAR2(32):='application/pdf';
BEGIN
   -- create the URL
   v_rest_url:=v_server_url||v_report_path||v_report_name||'.pdf';
 
   DBMS_OUTPUT.put_line('--Info  -- get the report with '||v_rest_url);
 
   v_file := apex_web_service.make_rest_request(   p_url     => v_rest_url
                        ,  p_http_method => 'GET'
                        ,  p_parm_name   => apex_util.string_to_table('project_id')
                        ,  p_parm_value  => apex_util.string_to_table('1')
                        ,  p_username    => v_user
                        ,  p_password    => v_pwd );
 
   v_file_length:=DBMS_LOB.getlength (v_file);
   DBMS_OUTPUT.put_line('--Info  -- get the file ::'||SUBSTR(v_file,0,100)||' Length:'||v_file_length);
 
   -- create blob
   DBMS_LOB.createtemporary(v_blob, TRUE);
 
   -- read the file
   -- alternativ  dbms_lob.converttoblob
   v_amount := 32000;
   v_offset := 1;
   BEGIN
       LOOP
		   -- read as clob
		   DBMS_LOB.read( v_file, v_amount, v_offset, v_buffer );
		   DBMS_OUTPUT.put_line('info -- read:'||TO_CHAR(v_offset));
		   -- convert to blob
		   v_buffer_raw := UTL_RAW.cast_to_raw(v_buffer);
		   DBMS_LOB.writeappend(v_blob, UTL_RAW.LENGTH(v_buffer_raw), v_buffer_raw);
		   -- next loop
		   v_offset := v_offset + v_amount;
		   v_amount := 32000;
       END LOOP;
   EXCEPTION
       WHEN NO_DATA_FOUND THEN
        NULL;
   END;
   --- 
   v_file_length:=DBMS_LOB.getlength (v_blob);
   BEGIN
    --send the file via APEX
		OWA_UTIL.mime_header (NVL (v_mime_type, 'application/pdf'), FALSE);
		HTP.p ('Content-length: ' || v_file_length);
		htp.p('Content-Disposition:  attachment; filename="'|| v_report_name ||'.pdf"');
		owa_util.http_header_close;
		wpg_docload.download_file (v_blob);
		apex_application.stop_apex_engine;
	EXCEPTION WHEN OTHERS THEN
		htp.prn('error: '||SQLERRM);
		apex_application.stop_apex_engine;
   END;
END;
/

Der Bericht kann nun über SQL*Plus testweise aufgerufen werden!

Für HTTPS muss allerdings noch eine Wallet hinterlegt werden!

Jetzt muss das nur noch in Apex eingebunden werden.

Auf die Schnelle in einer neuen leeren Apex Seite im Pre-Rendering Bereich im Before Header als Prozesse obigen Code hinterlegt, leider gibt das noch Bruch, wie befürchtet, schlägt hier etwas mit Zeichensatzwandel zu.

Da ein CLOB zurückgeben wird kommt es zu ersten Zeichensatzwandlungen, schlecht …. so funktioniert das nicht!

Lösung mit UTL_HTTP

Um nun ein BLOB als Ergebniss zu erhalten, muss mit UTL_HTTP gearbeitet werden.

ACL auf dem APEX Schema Owner setzen!

   DECLARE
      v_apex_server VARCHAR2(256):= '10.10.10.180';
    BEGIN
        DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
               host       => v_apex_server
            ,  lower_port => 80
            ,  upper_port => 80
            ,  ace        => xs$ace_type(privilege_list => xs$name_list('connect'),
                                         principal_name => 'GPI_APEX_SCHEMA',
                                         principal_type => xs_acl.ptype_db)
        );
    END;
    /

So gehts, sehr grob, aber muss jetzt Abend essen kochen:

DECLARE
    -- URL to call
    v_url      VARCHAR2(2000):='http://10.10.10.180/jasperserver/rest_v2/reports/Primavera/PrimaTest.pdf';
 
    -- Login data
    v_username VARCHAR2(2000):='gpi';
    v_password VARCHAR2(2000):='gpi';
    v_realm    VARCHAR2(2000):=NULL;
    v_scheme   VARCHAR2(256):=NULL;
 
    -- get the URL Data 
    v_req      UTL_HTTP.req;
    v_resp     UTL_HTTP.resp;
    v_blob     blob;
    v_buf_blob blob;
 
    v_file_length PLS_INTEGER :=0;
    v_mime_type VARCHAR2(34)  :='application/pdf';
    v_report_name VARCHAR2(45):='primatest.pdf';
    v_error_log   VARCHAR2(32000):='<html><body>';
    v_error       BOOLEAN := TRUE;
BEGIN
 
  -- Handle the http return code in this script
  UTL_HTTP.set_response_error_check(FALSE);
 
  -- Start the http request
  v_req := UTL_HTTP.begin_request(v_url);
  -- use in this example http basic authen. scheme
  UTL_HTTP.set_authentication(v_req, v_username, v_password); 
 
  -- do the request 
  v_resp := UTL_HTTP.get_response(v_req);
 
  -- check if the login was sucessfull
  IF (v_resp.status_code = UTL_HTTP.http_unauthorized) THEN
    -- check the answer of the server 
	UTL_HTTP.get_authentication(v_resp, v_scheme, v_realm, FALSE);
	v_error_log:=v_error_log||'-- Info : web server is protected '||UTL_TCP.CRLF;
    v_error_log:=v_error_log||'-- Info : Scheme ' || v_scheme || ' realm ' || v_realm || ' user credential nessesary'||UTL_TCP.CRLF;
    -- end the request
	UTL_HTTP.end_response(v_resp);
    v_error:=TRUE;
  ELSIF (v_resp.status_code = UTL_HTTP.http_proxy_auth_required) THEN
    UTL_HTTP.get_authentication(v_resp, v_scheme, v_realm, FALSE);
    v_error_log:=v_error_log||'-- Info : web proxy is protected '||UTL_TCP.CRLF;
    v_error_log:=v_error_log||'-- Info : Scheme ' || v_scheme || ' realm ' || v_realm || ' user credential nessesary'||UTL_TCP.CRLF;
    -- end the request
	UTL_HTTP.end_response(v_resp);
    v_error:=TRUE;
  ELSIF (v_resp.status_code = UTL_HTTP.HTTP_NOT_FOUND ) THEN
    UTL_HTTP.end_response(v_resp);
    v_error_log:=v_error_log||'-- Info : get 404 Page not found  '||v_url||UTL_TCP.CRLF;
    v_error:=TRUE;
  ELSIF (v_resp.status_code > 299 ) THEN
    UTL_HTTP.end_response(v_resp);
    v_error_log:=v_error_log||'-- Info : Get this result from the webserver:  '||TO_CHAR(v_resp.status_code)||UTL_TCP.CRLF;
    v_error:=TRUE;   
  ELSE
    v_error:=FALSE;
  END IF;
 
  -- create the  blob to hold the result of the request
  DBMS_LOB.createtemporary( v_blob, TRUE );
  -- readhe responmse 
  --
  BEGIN
     LOOP
       UTL_HTTP.read_raw( v_resp, v_buf_blob );
       DBMS_LOB.append( v_blob, v_buf_blob );
     END LOOP;
   EXCEPTION
     WHEN UTL_HTTP.end_of_body
     THEN
       NULL;
  END; 
  -- close the response	
  UTL_HTTP.end_response(v_resp);
 
  -- send the data
  v_file_length:=DBMS_LOB.getlength (v_blob);
  v_error_log:=v_error_log||'-- Info :  lenght resut '|| ': ' || TO_CHAR(v_file_length)||UTL_TCP.CRLF;
 
  -- if no error send the blob as pdf
  IF   v_error = FALSE THEN 
    BEGIN
     owa_util.mime_header (v_mime_type, FALSE);
	 htp.p ('content-length: ' || v_file_length);
	 htp.p('content-disposition:  attachment; filename="'|| v_report_name ||'.pdf"');
	 owa_util.http_header_close;
	 wpg_docload.download_file (v_blob);
	EXCEPTION WHEN OTHERS THEN
	 v_error_log:=v_error_log||'-- Info : Error Send File: '||SQLERRM;
	 v_error:=TRUE;  
    END;
   END IF;
   IF v_error = TRUE THEN 
    owa_util.mime_header ('text/html', FALSE);
    owa_util.http_header_close;
    v_error_log:=v_error_log||'</pre></body></html>';
    htp.p(v_error_log);
    -- to debug strange results
	--if v_blob is not null then 
    --  wpg_docload.download_file (v_blob);
    --end if;	
   END IF;
   -- end the apex process
   apex_application.stop_apex_engine;
 
EXCEPTION WHEN OTHERS THEN
   owa_util.mime_header ('text/html', FALSE);
   owa_util.http_header_close;
   v_error_log:=v_error_log||SQLERRM||' </pre></body></html>';
   htp.p(v_error_log);
   apex_application.stop_apex_engine;
END;

Die Authorisierung finden Sie hier ⇒ Handling HTTP Authentication mit UTL_HTTP https://docs.oracle.com/database/121/ARPLS/u_http.htm#i1013202

Schlussfolgerung:

D.h. wir können mit „apex_web_service“ den Login vorbereiten, das dabei erzeuge Cookie muss dann in den PL/SQL Scope gespeichert und gesichert werden.

Synchron:

  • Dann können wir gleich synchron mit UTL_HTTP den Bericht rufen und aus der DB verwenden.

Asynchron:

  • Hier können wir wieder mit apex_web_service alles vorbereiten und dann den eigentliche Bericht mit UTL_HTTP abholen

Jetzt nähern wir uns stark der Lösung von Dietmar mit UTL_HTTP, eine einfache Umsetzen mit APEX_WEB_SERVICE sehe ich hier auf die schnelle nicht, da es anscheinend keine Möglichkeit gibt, den Return einer Rest Calls als BLOB zu erhalten, jeder Verarbeitung eines CLOB führt aber zu Zeichensatzprobleme und zerstöret damit die Stuktur einer PDF Daten.


Quellen

Cookies helfen bei der Bereitstellung von Inhalten. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Rechner gespeichert werden. Weitere Information
"Autor: Gunther Pipperr"
prog/oracle_apex_jasper_reports.txt · Zuletzt geändert: 2016/07/11 22:30 von Gunther Pippèrr