Blog: Latest Entries (15):


curl auf Windows

Wer mal curl benötigt aber gerade nur Windows zur Verfügung hat, kann es sich leicht nach installieren. Oracle hat eine gute kleine Anleitung veröffentlicht.

Bei mir reichte es dir Schritte 3 und 4 auszuführen.

http://www.oracle.com/webfolder/technetwork/tutorials/obe/cloud/objectstorage/restrict_rw_accs_cntainers_REST_API/files/installing_curl_command_line_tool_on_windows.html

PHP Object-Mapping mit PDBC

Als ich mit PHP anfing war es noch Alltag, dass man wenn eine Datenbank-Abfrage notwendig war diese auch einfach direkt an Ort und Stelle einbaute. Kapselung in einer eigenen Funktion oder Klasse war nicht wirklich verbreitet. Es wurde meistens direkt mit den entsprechenden Datenbank Funktionen gearbeitet und keine Abstraktion verwendet.

Nur in großen Anwendungen war es anders. Vorher hatte ich mit Java, JDBC und DataSources im Tomcat gearbeitet, wo es am Ende egal war welche Datenbank dahinter lief und man dies nur beim Anlegen der DataSource abgeben musste. Zuhause war ich auf Oracle Datenbanken und hatte nur so am Rande mit MySQL zu tun. Ich sollte ein Projekt von MySQL auf Oracle portieren und merkte schnell wie doof es war keine Abstraktion zu haben. Also nahm ich mir vor, sollte ich mal ein PHP anfangen, dass ich es besser machen würde. Genau so einfach wie mit einem Tomcat und den DataSources.

Dann kam in der Berufsschule das Thema Datenbanken, dass wir mit einer XAMPP-Installation bearbeiten sollten. Ein kleines Shop-"System". CRUD, eine Liste laden und wenn wir voll gut waren sogar Joins oder mal ein COUNT mit GROUP BY.. also alles was die meistens aus ihrem normalen Arbeitstag in und auswendig kannten. Ich wollte es dann auch gerne so schreiben, dass ich SQL über eine zentrale Klasse ausführen konnte und Schleifen einfach ausführen konnte und das Problem das bei Oracle ein Array so aussah [column]<()tr> und nicht wie bei MySQL <()tr>[column] direkt dort in der Implementierung ausgeglichen wurde und man so einfach zwischen den beiden Systemen switchen kontne ohne Code ändern zu müssen.

So entstand PDBC.

Aber fangen wir mal an. Zuerst erstellen wir uns eine einfache Datenbank Tabelle mit ein paar Daten. Diese werden dann im Verlaufe dieses Artikels mit PDBC aus der Datenbank laden und einmal ausgeben. Unsere Tabelle sieht wie folgt aus:


CREATE TABLE tests (
test_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
test_name VARCHAR(255) NOT NULL,
PRIMARY KEY (test_id)
);

INSERT INTO tests(test_name) VALUES ('TEST_01');
INSERT INTO tests(test_name) VALUES ('TEST_02');
INSERT INTO tests(test_name) VALUES ('TEST_03');
INSERT INTO tests(test_name) VALUES ('TEST_04');
INSERT INTO tests(test_name) VALUES ('TEST_05');


Diese Tabelle repräsentiert Daten für eine einfache Klasse mit einer Id und einem Namen. Also das Grundmodel auf dem bestimmt fast 98% aller Entitäten basieren. Das normale vorgehen wäre jetzt diese Daten über ein einfaches SQL Query einzulesen. Dabei enthält man ein ResultSet oder Array mit allen Zeilen, die man dann in einer Schleife durchläuft. Während jedem Durchlauf erzeugt man ein neues Objekt der Klasse und befühlt dieses über die Setter mit den Daten aus dem Zeilen Array.


$result=array();
foreach($data as $row){
$obj=new Test();
$obj->setId($row["test_id"]);
$obj->setName($row["test_name"]);
$result[]=$obj;
}


Man muss viel per Hand schreiben, was sehr zeitintensiv, stupide und fehleranfällig ist. Wenn man an mehreren Stellen so lädt z.B. einmal laden eines Objekt anhand der Id, einmal die Liste und noch die Liste mit einem Pattern für den Namen, muss man alle Methoden und Funktionen wo man die Daten in das Objekt füllt ändern, wenn man mal eine Column der Datenbank hinzufügt, ändert oder entfernt.
Wenn man diesen Teil nun automatisiert, erleichtert einen das Leben schon sehr. Bei einer Klasse mit 10 Attributen sind es dann nicht mehr 12 Zeilen Code sondern nur noch 2. PDBC erledigt für einen diese Schritte. Vom erzeugen der Objekt, die Abfrage der Daten und dann das befüllen der Objekte.
Um nun zu wissen, welches Datenfeld aus der Datenbank in welches Klassen-Attribute gehört werden Annotationen genutzt. Es ist ganz leicht JPA und Hibernate beeinflusst. Wobei PDBC sehr viel weniger kann.
FKs werden nicht in Objekte abgebildet, also ein lazy oder eager Loading. Es werden nur wirklich Daten kopiert. Das SQL muss man komplett per Hand schreiben, was ich aber als Vorteil sehe, weil HQL/JPQL doch mich immer sehr eingeschränkt haben und viele wichtige SQL-Funktionen wie Nested-Queries und gute Joins oft nicht möglich waren. SQL bietet dort sehr viel mehr und performantere Möglichkeiten.

Aber hier geht es erst einmal um einfaches und schnelles Laden der Datensätze.

Dafür brauchen wir erst einmal unsere Klasse mit den entsprechenden Annotationen, die bestimmen zu welcher Column das Klassen-Attribute gehört. Das Schlüsselwort hierbei ist "@dbcolumn". Diese Annotationen werden mit Hilfe der Refelection Klassen von PHP und den DocComments gelesen und dann geparst.

Code-Auszug zum Lesen der Annotationen:

private function getColumnName($prop){
$result=null;
$doc=$prop->getDocComment();
if(preg_match("/@dbcolumn=[a-zA-Z0-9_]+/",$doc)){
$result=preg_replace("/^.+@dbcolumn=([a-zA-Z0-9_]+)\s.+$/Uis","$1",trim($doc));
}
return $result;
}


Damit man hier performant bleibt werden die Properties einmal pro Klasse eingelesen und in einer Map gespeichert, so was man schnell über den Column-Name zum Property gelangt. Wenn man dann für die nächste Klasse die Zuordnung braucht hat man diese schon. Da die Map static ist, geschieht das Einlesen auch nur einmal pro Request für eine Klasse.


private function loadProperties($ref){
if(isset(self::$cache[$ref->getName()])){
return self::$cache[$ref->getName()];
}
else{
$props=$ref->getProperties();
$map=array();
foreach($props as $prop){
$prop->setAccessible(true);
$map[$this->getColumnName($prop)]=$prop;
}
self::$cache[$ref->getName()]=$map;
return self::$cache[$ref->getName()];
}
}


setAccessible(true) ist hier sehr wichtig, weil wir so private Attribute lesen und schreiben können und dazu bringt es noch einem wirklich spürbaren Performance-Vorteil. Ja.. auch bei PHP. Bei Java gilt genau das Selbe. Die Reflection-Klassen von PHP sind von den Bezeichnungen und Methoden auch sehr an Java angelehnt und wer mit den Relfections in Java sich auskennt, kann sofort in PHP weiter machen, da die wichtigen Methodennamen wirklich zu 100% übereinstimmen.

Hier also unsere Test-Klasse:

class Test{
/**
* @dbcolumn=test_id
*/
private $id=0;
/**
* @dbcolumn=test_name
*/
private $name="";

public function __construct(){

}

public function getId(){
return $this->id;
}
public function setId($id){
$this->id=$id;
}
public function getName(){
return $this->name;
}
public function setName($name){
$this->name=$name;
}
}


Man sieht hier die Annotationen an den Attributen der Klasse. Die Werte werden direkt in die Attribute geschrieben. Die Nutzung der Getter und Setter ist hier nicht implementiert. Wobei das große Vorteile hätte da man so z.B. eine "1" aus der Datenbank auf ein "true" mappen könnte. Dafür werde ich mal eine Implementierung mit eignen Properties schreiben, die zusammen mit dem Property auch die Accessor-Methoden mitliefert, wenn diese vorhanden sind.

Jetzt geht es aber weiter mit dem konkreten Beispiel. Zuerst werden wir uns unsere Datenbank-Klasse holen. Dafür muss die Datenbankverbindung in der Config-Datei angegeben sein. Der Name muss eindeutig sein und mit dessen Hilfe wird die Datenquelle auch identifiziert. Die Bezeichnungen in der Config-Datei orientieren sich Oracle, so nennt sich ein Feld "SID", also Service-ID, das entspricht bei MySQL dem Datenbanknamen. Sollte hier der Standard-Port von MySQL verwendet werden, ist dieser normaler weise nicht anzugeben.


<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="datasources.xsl"?>
<datasources>
<datasource name="ds_test" type="DBMySQLi">
<host>localhost</host>
<port>3306</port>
<sid>test</sid>
<username>root</username>
<userpassword>bitnami</userpassword>
</datasource>
</datasources>


Wir haben hier also unsere Datasource (wie im Tomcat) unter dem Namen "ds_test", die auch die lokale MySQL-Instanz und die Datenbank verweist. Wie man am Passwort schon vermuten kann, habe ich bei mir einfach den Bitnami WAMP-Stack verwendet, aber XAMPP oder eine vollständige Installation gehen natürlich genau so gut.

Wir müssen noch angeben wo die Config-Datei und wo die Datenbank-Klassen zu finden sind.


include_once("system/PDBC/PDBCDBFactory.php");
PDBCDBFactory::init("system/PDBC/dbclasses/","system/PDBC/conffiles/");


Wenn man PDBC in sein System integrieren will, kann man so die Config-Datei einfach mit im Config-Verzeichnis des Systems unterbringen.

Nun folgt ein kleines und einfaches SQL-Statement:


$sql="SELECT test_id,test_name FROM tests ORDER BY test_name ASC";


Und nun beginnt der spannende Teil, wir übergeben unser Datebank-Object, den SQL-String und den Klassennamen an den PDBCObjectMapper. Dieser nutzt das Interface der DB-Klasse, weswegen ihm egal ist welche Implementation verwendet wird. Dann wird die Klasse analysiert und deren Properties eingelesen und die passenden Spalten wie schon weiter oben beschrieben eingelesen. Der Rest ist eigentlich relativ primitiv. Erst wird eine Instanz der Klasse erzeugt und dann das Array mit dem Datensatz des Resultsets wir mit $key (Spaltenname) und $value (Wert aus der DB) durchlaufen.
Wird ein Property zu dem $key gefunden wird in dieses das $value geschrieben.


private function fillObject($row,$ref){
$obj=$ref->newInstance();
$props=$this->loadProperties($ref);
foreach($row as $key => $value){
if(isset($props[$key])){
$props[$key]->setValue($obj,$value);
}
}
return $obj;
}

public function queryList($db,$sql,$className){
$db->executeQuery($sql);
$ref=new ReflectionClass($className);
$result=array();
for($i=0;$i<$db->getCount();$i++){
$result[count($result)]=$this->fillObject($db->getRow($i),$ref);
}
return $result;
}


Die loadProperties-Methode habe ich ja weiter oben schon beschrieben. Hier wieder sich daran erinnern, dass wir setAccessible(true) für jedes Properties gesetzt haben und somit ohne Probleme in private-Properties schreiben können.

Im Aufruf sieht das ganze dann so aus:


$mapper=new PDBCObjectMapper();
$tests=$mapper->queryList($db, $sql, "Test");


"Test" ist unser Klassename, $sql unser String mit dem Query-Statement und $db unsere oben in der config-Datei definierten Datasource.
Als Rückgabe erhalten wir, wie alle schon sicher richtig vermutet haben, ein Array mit unseren gefüllten Test-Objekten. Um zusehen, dass alles richtig befüllt wurde, geben wir diese einfach mal aus.


foreach($tests as $test){
echo $test->getId()." - ".$test->getName()."<br>\n";
}


Der gesamte Code für unser kleines Test-Script sieht dann also so aus:

include_once("system/PDBC/PDBCDBFactory.php");
PDBCDBFactory::init("system/PDBC/dbclasses/","system/PDBC/conffiles/");

class Test{
/**
* @dbcolumn=test_id
*/
private $id=0;
/**
* @dbcolumn=test_name
*/
private $name="";

public function __construct(){

}

public function getId(){
return $this->id;
}

public function setId($id){
$this->id=$id;
}

public function getName(){
return $this->name;
}

public function setName($name){
$this->name=$name;
}
}

$db=PDBCCache::getInstance()->getDB("ds_test");
$sql="SELECT test_id,test_name FROM tests ORDER BY test_name ASC";
$mapper=new PDBCObjectMapper();
$tests=$mapper->queryList($db, $sql, "Test");
foreach($tests as $test){
echo $test->getId()." - ".$test->getName()."<br>\n";
}


Eine passende PDBC-Version ist hier zu finden.

Ich hoffe ich konnte einen kleinen Einblick geben, wie man sich die Arbeit mit Datenbanken in PHP einfacher machen kann und einen Teil der Konzepte aus der Java-Welt in PHP implementieren kann. Man kann natürlich noch sehr viel mehr implementieren und ein kompletes und komplexes ORM-System hier draus bauen, aber das würde hier zu weit führen und ich brauchte es auch so erst einmal nicht. Auch Inserts und Updates darüber zu realisieren und Getter und Setter benutzen zu können wäre toll und steht noch auf dem Plan, aber wohl alles erst im Laufe von 2016.

Bar-Diagramme mit JavaScript und Canvas

bbcode-image


Wer solche Diagramme mit dem Canvas Element und etwas Javascript zeichnen möchte, kann gerne diese von mir geschriebene Klasse verwenden:


function BarChartFactory(canvasElement,unitName,plotEvery){
this.canvas=canvasElement;

this.maxValue=0;
this.categoryTitleWidth=50;
this.valueTitleWidth=50;
this.bottomScalaHeight=10;
this.padding=5;
this.linesSeperateValues=true;

this.barStart=0;
this.barEnd=0;
this.barTranslationFactor=1;
this.barHeight=20;
this.barPadding=4; //top + bottom

this.xPlotEvery=plotEvery;
this.unitName=unitName;

this.categoryValueSize=2; //if more.. ignore.. if less.. paint empty

this.categories=[];

this.ctx=null;
this.width=200;
this.height=0;

this.addValue=function(categoryTitle,valueTitle,value,valueColor){
var cat=null;
for(var ii=0;ii<this.categories.length && cat==null;ii++){
if(this.categories[ii].title==categoryTitle){
cat=this.categories[ii];
console.log("found existing category: "+categoryTitle);
}
}

if(cat==null){
cat={title:"",values:[]};
cat.title=categoryTitle;
this.categories[this.categories.length]=cat;
}

cat.values[cat.values.length]={value:value,title:valueTitle,color:valueColor};
};

this.init=function(ctx,width){
this.barStart=this.categoryTitleWidth+this.valueTitleWidth;
this.barEnd=width;

for(var ii=0;ii<this.categories.length;ii++){
var cat=this.categories[ii];
for(var j=0;j<cat.values.length;j++){
if(cat.values[j].value>this.maxValue){
this.maxValue=cat.values[j].value;
}
}
if(cat.values.length>this.categoryValueSize){
this.categoryValueSize=cat.values.length;
}
}
this.maxValue=this.maxValue+(this.maxValue/10);

this.barTranslationFactor=(this.barEnd-this.barStart)/this.maxValue;

this.ctx=ctx;
this.ctx.canvas.width=width+(this.padding*2);
this.ctx.canvas.height=(this.categories.length*this.categoryValueSize*this.barHeight)+this.bottomScalaHeight+(this.padding*2);

this.width=this.ctx.canvas.width;
this.height=this.ctx.canvas.height;

//TODO clearRect
};

this.paintStructure=function(){
//paint main structure
this.ctx.strokeStyle="#AAAAAA";
this.ctx.beginPath();
this.ctx.moveTo(this.categoryTitleWidth+this.valueTitleWidth,0+this.padding);
this.ctx.lineTo(this.categoryTitleWidth+this.valueTitleWidth,this.height-this.barHeight+this.barPadding);
this.ctx.stroke();

if(this.xPlotEvery>0){
console.log("paint structure ("+this.maxValue+")");
var xStart=this.categoryTitleWidth+this.valueTitleWidth;
for(var i=this.xPlotEvery;i<this.maxValue;i=i+this.xPlotEvery){
this.ctx.beginPath();
this.ctx.moveTo(xStart+(i*this.barTranslationFactor),0+this.padding);
this.ctx.lineTo(xStart+(i*this.barTranslationFactor),this.height-this.barHeight+this.barPadding);
this.ctx.stroke();

this.ctx.fillStyle="#000000";
this.ctx.fillText(""+Math.round(i)+this.unitName,xStart+(i*this.barTranslationFactor), this.height-this.barHeight+this.barPadding+12);
}
}
};

this.paint=function(width){
this.init(this.canvas.getContext("2d"),width);

this.ctx.lineWidth=1;
this.ctx.strokeStyle="#000000";
this.paintStructure();
this.ctx.strokeStyle="#000000";

for(var iC=0;iC<this.categories.length;iC++){
var cat=this.categories[iC];

this.ctx.fillStyle="#000000";
this.ctx.fillText(cat.title,this.padding,(((iC)*this.categoryValueSize)+Math.round(this.categoryValueSize/2))*this.barHeight+this.padding);

for(var i=0;i<this.categoryValueSize;i++){
var value={value:0,title:"",color:"#FF0000"};
if(cat.values){
value=cat.values;
}

this.ctx.fillStyle="#000000";
this.ctx.fillText(value.title,this.categoryTitleWidth,((iC*this.categoryValueSize)+i+1)*this.barHeight);

//line under value title
this.ctx.beginPath();
if(i==this.categoryValueSize-1){
this.ctx.moveTo(this.padding,((iC*this.categoryValueSize)+i+1)*this.barHeight+this.padding);
}
else{
this.ctx.moveTo(this.categoryTitleWidth,((iC*this.categoryValueSize)+i+1)*this.barHeight+this.padding);
}

if(this.linesSeperateValues){
this.ctx.lineTo(this.width-this.padding,((iC*this.categoryValueSize)+i+1)*this.barHeight+this.padding);
}
else{
this.ctx.lineTo(this.categoryTitleWidth+this.valueTitleWidth,((iC*this.categoryValueSize)+i+1)*this.barHeight+this.padding);
}

this.ctx.stroke();

//paint real value
this.ctx.beginPath();
this.ctx.rect(this.categoryTitleWidth+this.valueTitleWidth,((iC*this.categoryValueSize)+i)*this.barHeight+this.padding+this.barPadding,value.value*this.barTranslationFactor,this.barHeight-(this.barPadding*2));
this.ctx.stroke();
this.ctx.fillStyle=value.color;
this.ctx.fill();
}
}
};
}


Das Beispiel zum Bild sieht dann so aus:


<html>
<head>
<title>2dbar test</title>
<script type="text/javascript" src="./BarChartFactory.js"></script>
</head>
<body>
<canvas id="can" style="border:1px solid #000000;border-radius:4px;">
</canvas>
<script type="text/javascript">
var bcf=new BarChartFactory(document.getElementById("can"),"fps",10);
bcf.addValue("386SX","16 Mhz",20,"#70B4B6");
bcf.addValue("386SX","25 Mhz",25,"#0000FF");
bcf.addValue("386DX","25 Mhz",40,"#70B4B6");
bcf.addValue("386DX","33 Mhz",60,"#70B4B6");
bcf.paint(400);
</script>
</body>
</html>

Soft Skills - Buch

Heute zum Geburtstag habe ich von meiner Frau Soft Skills geschenkt bekommen. Es ist von John Z. Sonmez, dem Besitzer von http://simpleprogrammer.com
und ich habe schon sehr viel Gutes darüber gehört und gelesen. Ein Buch für Programmierer bei dem das Programmieren und die Technik nicht im Mittelpunkt stehen, sondern das ganze Drumherum. Wie auch auf dem Buchcover steht: "coding is the fun part". Kommunikation, Produktivität und Stress sind der nicht so spannende Teil, aber man muss da immer durch.

Wenn ich es lese werde ich sicher noch ein oder zwei mal meine Gedanken dazu hier veröffentlichen.

bbcode-image

FileIOToolKit neuste Version

Hier dann die neuste Version des FileIOToolKit .



import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

public class FileIOToolKit {

public FileIOToolKit() {

}

public static byte[] read(String filename) throws IOException {
return read(new File(filename));
}

public static byte[] read(File file) throws IOException {
byte[] in;
try (FileInputStream fis = new FileInputStream(file);) {
in = new byte[(int) file.length()];
fis.read(in);
}
catch (Exception e) {
throw e;
}
return in;
}

public static String readString(File file) throws IOException {
return readString(file, true);
}

public static String readString(File file, boolean reConstructEndOfLines) throws IOException {
StringBuilder buf = new StringBuilder();
try (BufferedReader in = new BufferedReader(new FileReader(file))) {
String str;
while ((str = in.readLine()) != null) {
if (reConstructEndOfLines) {
buf.append(str + "\n");
}
else {
buf.append(str);
}
}
}
catch (IOException e) {
throw e;
}
return buf.toString();
}

public static List<String> readStringAsList(File file) throws IOException {
List<String> buf = new ArrayList<String>();
try (BufferedReader in = new BufferedReader(new FileReader(file))) {
String str;
while ((str = in.readLine()) != null) {
buf.add(str);
}
}
catch (IOException e) {
throw e;
}
return buf;
}

public static String readString(String filename) throws IOException {
return readString(new File(filename));
}

public static String readString(String filename, boolean reConstructEndOfLines) throws IOException {
return readString(new File(filename), reConstructEndOfLines);
}

public static void write(File file, byte[] content) throws IOException {
try (RandomAccessFile rFile = new RandomAccessFile(file, "rw")) {
rFile.write(content);
}
catch (IOException e) {
throw e;
}
}

public static void write(String filename, byte[] content) throws IOException {
write(new File(filename), content);
}

public static void writeString(String filename, String content) throws IOException {
writeString(new File(filename), content, "UTF-8");
}

public static void writeStringOld(String filename, String content) throws IOException {
writeStringOld(new File(filename), content, "UTF-8");
}

public static void writeString(String filename, String content, String encoding) throws IOException {
writeString(new File(filename), content, encoding);
}

public static void writeString(File file, String content) throws IOException {
writeString(file, content, "UTF-8");
}

public static void writeString(File file, String content, String encoding) throws IOException {
try (RandomAccessFile rFile = new RandomAccessFile(file, "rw")) {
rFile.writeBytes(content);
}
catch (IOException e) {
throw e;
}
}

public static void writeStringOld(File file, String content, String encoding) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), encoding))) {
bw.write(content);
}
catch (IOException e) {
throw e;
}
}

public static void append(File file, byte[] content) throws IOException {
try (FileOutputStream fos = new FileOutputStream(file, true)) {
fos.write(content);

}
catch (IOException e) {
throw e;
}
}

public static void append(String filename, byte[] content) throws IOException {
append(new File(filename), content);
}

public static void appendString(File file, String content) throws IOException {
appendString(file, content, "UTF-8");
}

public static void appendString(File file, String content, String encoding) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), encoding))) {
bw.write(content);
}
catch (IOException e) {
throw e;
}
}

public static void appendString(String filename, String content) throws IOException {
appendString(new File(filename), content);
}

public static void appendString(String filename, String content, String encoding) throws IOException {
appendString(new File(filename), content, encoding);
}

private static String convertByteArrayToHexString(byte[] arrayBytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int ii = 0; ii < arrayBytes.length; ii++) {
stringBuffer.append(Integer.toString((arrayBytes[ii] & 0xff) + 0x100, 16).substring(1));
}
return stringBuffer.toString();
}

public static String createMD5HashOfFile(String filename) throws IOException, NoSuchAlgorithmException {
return createMD5HashOfFile(new File(filename));
}

public static String createMD5HashOfFile(File file) throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] content = read(file);
byte[] hash = digest.digest(content);
return convertByteArrayToHexString(hash);
}

public static String createMD5HashOfContent(byte[] content) throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] hash = digest.digest(content);
return convertByteArrayToHexString(hash);
}
}


Alles was man für die alltägliche Arbeit mit Dateien in Java braucht.

Performance Analyse auf Oracle DB

Ein wenig besinnliches zu Weihnachten. Performance-Analyse auf Oracle Datenbanken. Wenn man heraus finden will welche SQLs viel Zeit verbrauchen, kann man das Query hier verwenden. Es ist ein erster Ansatz und keine absolute Lösung. Aber wenn man Probleme hat, ist es ein guter Einstieg in die Analyse.


select ROUND(disk_reads/decode(executions,0,1,executions)/300) EXTIME, SQL_TEXT
from v$sql
where disk_reads/decode(executions,0,1,executions)/300 >1
and executions>0
order by (disk_reads/decode(executions,0,1,executions)/300) DESC


Oft hilft es am Ende einfach einen Index zu setzen. Aber manchmal muss man tiefer in die Materie gehen.

How to: Upload Files with JavaScript FileAPI - 3 of 3

We have out data seperated in chunks now. Now we have to write a script to send this data to the PHP-script or servlet to save it in a file. This is the elementary part of the the javascript-function and also the simple part. Because we append every chunk to the final file, we can’t work with asynchron requests. If you want to, you have to send also the number of the chunk, and also the final filesize to create a dummy-file and replace the part of the sent chunk. For the javascript the changes are no this big, but the server-side script become more complex.

So.. we will use the synchrone style and work with a simple for-loop to run through our array with the data chunks.


var result=null;
for(var i=0;i<chunks.length;i++){
var last=false;
if(i==(chunks.length-1)){
last=true;
}
result=uploadFileChunk(chunks,filenamePrefix+file.name,url,params,last);
}
return result;


Only the result of the request of the last chunk is returned. Because it is the chunk where the magic (copy from temp-folder to real target folder and create and save entity to database, etc) happens.

I send add a prefix to the filename to create a filename like userid+prefix+filename+fileextension (like *.part or somethin like this). So a user can upload more than one file with the same filename, without cause problems on server side.

But you should also send the original filename as something like "clearname". In this example you can add it to die params-array (params["clearname"]=file.name;) and use it as filename in the db-entity to set this name in header-data on download of this file or in lists of the uploaded files.


function uploadFileChunk(chunk,filename,url,params,lastChunk) {
var formData = new FormData();
formData.append('upfile', chunk, filename);
formData.append("filename",filename);

for(key in params){
formData.append(key,params[key]);
}

if(lastChunk){
formData.append("lastChunk","true");
}
else{
formData.append("lastChunk","false");
}

var xhr = new XMLHttpRequest();

xhr.open("POST", url,false); //false=synchron;
xhr.send(formData);
console.log("upload response-text: "+xhr.responseText);
return xhr.responseText;
}


This is the code where the upload is peformed. It is a FormData-Object (like a form in HTML) and is send via a synchron AJAX-Request.

LG M237WDP mit scharfer Schrift am PC

Wer einen LG M237WDP an einem PC betreiben möchte muss auf jeden Fall DVI verwenden. HDMI liefert ein extrem matschiges Bild. Bei AMD/ATI Grafikkarten sollte der alternative DVI-Betriebsmodus deaktiviert sein. V-Schärfe und H-Schärfe laufen beide auf 44, danach tauchten bei mir Ghosting-Effekte bei der Schrift auf (Expert 1 Modus im Monitor gewählt).
Farbtemperatur habe ich auch über den Catalyst angepasst, da der Monitor selbst nicht genug Einstellungen hat.

Jetzt stimmen die Farben einiger Massen und auch die Schrift ist ansatzweise scharf.

How to: Upload Files with JavaScript FileAPI - 2 of 3

Now we have a file-object, we need to split it in small chunks to upload this chunks one by one. JavaScript have the ability to work with files. It can’t access the filesystem directly, but the open- and save-dialogs will do every thing we need. To save data for more than one session you can use the indexeddb. But here we need only open,save and drag and drop.


function createChunksOfFile(file,chunkSize){
var chunks=new Array();

var filesize=file.size;

var counter=0;
while(filesize>counter){
var chunk=file.slice(counter,(counter+chunkSize));
counter=counter+chunkSize;

chunks[chunks.length]=chunk;
}

return chunks;
}


The method is very simple. The loop runs as long as the copied size is smaller as the size of the file. With slice(from,to) you have to set the start- and the endpoint, that is read (in bytes). If the endpoint behind the real end of the file no execption is thrown and only the existing part will be copied. That makes it very easy to us. With every run of the loop we add 250000 to the copied-size till our copied-size is greater than the file-size. In ervery run of the loop we copy/slice the bytes from copied-size to copied-size + 250000 and add this slice-chunk to the output array.

Finally we get an array with alle the sliced chunks of the file in correct order.

Yes.. you can calculate the needed count of chunks and than use a for-loop and calculate the start and end for every part. Works great too… but i did it the other way.


var chunks=createChunksOfFile(file,250000);


So we have all chunks each ~250KB. 1MB should have ~4 Parts. Yes.. it’s 1024Bytes per 1KB.. but we keep it simple :-)

How to: Upload Files with JavaScript FileAPI - 1 of 3

A simple file-upload is easy to implement. A input-tag of the "file"-type. Set "method" to "post" and let the action attribute point to the target-page. Don’t forget enctype="multipart/form-data" and every thing is finished.

This implementation is very limited and in modern Web-Apps you want to do more than simply upload files. You want to scale, rotate and process the image. You want to see the progress of the upload and upload more than one file at once in the upload-queue. A few years earlier you had to use flash.. but flash is dead. Today we use HTML5 + JavaScript. With ist you can do what ever you want to do. The File-API helps you to load local files per input or drag and drop. A web-notification informs you when the upload is finished… very helpful if it is a very large file.

This file-upload that is created in this posts is not perfect, but it works in many of my projects and scripts. The target-script can be implemented very easy in PHP or as a servlet.

For a simple test you can use this PHP-script:

PHP:

<?php
//see $_REQUEST["lastChunk"] to know if it is the last chunk-part or not
if(isset($_FILES["upfile"])){
file_put_contents($_REQUEST["filename"],file_get_contents($_FILES["upfile"]["tmp_name"]),FILE_APPEND);
}
?>


for Java you can use this (FileIOToolKit is a class of my own, it simply adds a byte[] to the end of a file or creates the file, if it doesn’t exist):


@Override
protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
try {
String appPath = request.getServletContext().getRealPath("");

String filename = request.getParameter("filename");
for (Part part : request.getParts()) {
if (part.getName().equals("upfile")) {
byte[] out = new byte[part.getInputStream().available()];
part.getInputStream().read(out);

FileIOToolKit.append(appPath + File.separator + filename, out);
System.out.println(appPath + File.separator + filename + " saved!");
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}


As you see, the files aren’t uploaded at once, but in multiple parts. So ist is very easy to track the progress of the upload.

To load a file in HTML5 is very easy. Drag and drop or the OnChange-Action of a file-input element delivers you a event, that contains a list of files.

Here is simple example to get the files from the event. The two events from the input and the one from the drag and drop are different.


var files = null; // FileList object
if(!nodragndrop){
files=evt.dataTransfer.files; //input
}
else{
files=evt.target.files; //drag and drop
}


But we don’t care about this part of loading and starts where we have the file-object. You also can create a binary-blob from an data-URL (from a canvas for example).

This is parts 1 of 3.

The german version can be find here

TIOBE: Java gewinnt, Java auf dem Desktop verliert

Ich weiß, dass der TIOBE-Index nicht immer wirklich aussagekräftig ist, aber er zeigt doch immer ganz gut Tendenzen an. Dieses Jahr ist Java eindeutig der Sieger. 6% Zuwachs im Vergleich zum Vorjahr sind schon sehr viel. Aber das führt mich wieder zu einem anderen Thema und einer Behauptung von mir. "Durch den Verlust der Desktop-GUIs wird Java keinen Schaden nehmen".

In der Zeit wo Java 6% zulegte, verlor Java auch viele Anteile im Bereich der Desktop-Anwendungen basierend auf Swing und JavaFX. Ich würde damit ist widerlegt, dass ohne den Desktopbereich, Java und die JEE-Welt Schaden nehmen würde.

Wenn man dieses Diagramm mit dem aus diesem Post über JavaFX vergleicht, sieht man dass Swing und JavaFX insgesamt einen ähnlichen Verlauf nahmen wie Java nur den letzten Aufschwung nicht mitgemacht haben. Die Stärken und Neuentwicklungen von Java liegen also wo anders und reichen insgesamt aus um Java insgesamt zu Pushen und die Verluste im Desktop-Bereich mehr als auszugleichen.

Java hat und hatte schon immer seine Stärken auf der Server-Seite und da ist wohl auch noch genug Luft nach oben. Konkurrenz gibt es mit PHP und Node.js genug und es wird wirklich spannend, ob PHP mit PHP7 wieder mal etwas zulegen kann. Von der Performance her lag Java schon immer weit vor PHP, aber jetzt sollte PHP etwas aufholen können. Es wird spannend.. auch wenn PHP sicher etwas Zeit benötigt. In der PHP-Welt werden Neuerungen irgendwie immer sehr skeptisch aufgenommen. Die Migration auf PHP7 ist meistens sehr einfach und schnell. Java lebt momentan von Microservices, Reactive-Programming und bietet auch Event-Driven Frameworks wie Vert.x als Konkurrenz zu Node.js.
Ich bin mir unsicher, ob PHP da mithalten kann. WebSockets und Server-Sent Events sind im Tomcat 8 von Haus aus dabei, während man bei PHP da noch mehr basteln muss.

Es wird jedenfalls ein spannendes nächstes Jahr.

Ein Nachruf auf Firefox OS?

bbcode-image


Keine Ahnung ob es schon zu früh ist, aber irgendwie klingt es doch sehr danach als wäre Firefox OS gescheitert. Gefühlt war gerade in den letzten Monaten erst wieder Bewegung in den Firefox OS Markt gekommen. Die Installationszahlen meiner Apps gingen jedenfalls mal wieder nach oben und dümpelten nicht ewig lange auf einem Level herum.

Ich hatte sogar überlegt, doch noch mal die Apps dort wieder zu überarbeiten und vielleicht auch mal was neues dafür zu beginnen, auch wenn ich Projekt-technisch doch sehr ausgelastet bin. Ok Firefox OS hatte bei der Wahl eines neuen Primär-Smartphones keine Change und ich habe mich dann für ein Lumia mit Windows entschieden, aber dennoch hatte ich mal geguckt, was da so angeboten wird. Auch außerhalb des absoluten low-Price Segements und etwas mit besserer Kamera. Gab es leider nichts.

Aber nun scheint die Zeit für Firefox OS als kommerzielles Produkt abgelaufen zu sein.

Ich finde es durch aus schade, weil es wirklich einfach war Apps dafür zu erstellen. Meistens musste man eine vorhandene HTML5-App nur um die Manifest-Datei ergänzen und schon war die App fertig. Man konnte zwischen gehostet und gepackt wählen, wobei viele Benutzer wohl gepackte Apps vorzogen, um Traffic zu sparen. Der Vorteil ist auch, sollte der Firefox Marketplace zeitnah keine Apps mehr anbieten (was wir mal für die Smartphone-Besitzer mit FFOS-Smartphone nicht hoffen wollen), kann mit wenig Code selbst noch eine alternative bauen. Etwas JavaScript und eine URL auf die gehostete Manifest-Datei und schon ist der App-Store fertig. Gepackte Apps sind ähnlich einfach zu installieren.

Zu Wünschen wäre meiner Meinung nach, dass sich das App-Format irgendwie durchsetzen kann und man auf die selbe einfache Weise HTML5-Apps für Android, Windows Phone oder iOS anbieten könnte.

Von kompletten Neuentwicklungen werde ich wohl nun absehen, aber meine alten Apps auch weiterhin pflegen. Denn Firefox OS ist ja nicht tot.. nur nicht mehr kommerziell.

aktuelle Route im ZF2 auslesen

Wenn man an Routen im Zend Framework 2 z.B. die Markierung der aktiven Seite im Navigationsmenü fest macht, ist es oft gut zu wissen welche Route greift. Wenn eine Markierung fehlt oder falsch gesetzt ist, kann man sich entweder durch die Routen-Definition suchen und so heraus finden in welche andere Route das Request lief oder man kann sich auch direkt ausgeben lassen welche Route gerade gewählt wurde. Für Debugging eine sehr praktische Sache.


<footer style="text-align:center;">
<?php
$sm = $this->getHelperPluginManager()->getServiceLocator();
$router = $sm->get('router');
$request = $sm->get('request');
$routeMatch = $router->match($request);
$route = $routeMatch->getMatchedRouteName();
?>
<i>(Route: <?=$route?>)</i>
</footer>

Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?