October 20th, 2011 | Tags:

It’s frustrating to find all your settings gone when you upgrade eclipse versions and if you’re pedantic about certain settings (like I am) it can take you almost an entire day to upgrade versions (with everything working just the way you like it). With Indigo, fortunately, none of the manual settings were necessary. There was a very nice import wizard that allowed me to import my old Helios installation. The wizard is in File->Import->Install->From existing installation.

I was really hoping everything would go as planned but it didn’t. (It’s okay, I had very little expectation it would) Maven refused to build any projects. I checked the maven version and it was the embedded one. I figured since this was a little older than the latest maven3 release, it could be causing problems so I changed the installation to use a manually installed version of maven.

No dice. After a few minutes of searching, I found the problem (or so I thought). The maven builder that eclipse was using was missing.

That’s odd. Maven integration is built into Indigo now so there is no reason why it shouldn’t find the builder. Not good. I tried a few things but then I found this very useful thread on stackoverflow. It turns out that the .project eclipse files weren’t updated correctly. I right clicked on the broken projects and saw an option to “Convert into maven project” in the Configure menu.

On clicking this option, eclipse “transformed” my project into a maven project. I checked the .project and noticed that it had added another builder in there. All good but the project still doesn’t build. Grrrr… Check the builders again and this is what I see

Why didn’t it remove the useless builder? If I uncheck the missing builder my build still doesn’t run! Argh.

I see a dependency which doesn’t look like it should be in my build path, this is added because of the old builder (which I hate by now).

Once I remove this dependency from the build path it works! I’d suggest that you not upgrade eclipse on a weekday like I did (on a Wednesday too!) because this is one of the more frustrating downsides of eclipse. Hopefully, the next upgrade will be easy(hah!).

0 comments (458 views)
July 20th, 2011 | Tags: ,

I am unwell and stuck at home so I decided to dive into my Android app (a new one this time!). I wanted to perform some tasks that could be long running (doubtfully but why freeze the UI thread ever). I want to show a ProgressDialog here but it doesn’t work with the static factory method, ProgressDialog.show(…). I keep getting the same exception again and again.

07-20 22:57:16.445: ERROR/AndroidRuntime(25843): FATAL EXCEPTION: main
07-20 22:57:16.445: ERROR/AndroidRuntime(25843): java.lang.NullPointerException
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.app.ProgressDialog.onProgressChanged(ProgressDialog.java:318)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.app.ProgressDialog.setMax(ProgressDialog.java:233)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at com.codercorp.android.playlist.ExporterTask.onPreExecute(ExporterTask.java:68)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.os.AsyncTask.execute(AsyncTask.java:391)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at com.codercorp.android.playlist.PlaylistExporterMain.onClick(PlaylistExporterMain.java:81)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.view.View.performClick(View.java:2485)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.view.View$PerformClick.run(View.java:9080)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.os.Handler.handleCallback(Handler.java:587)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.os.Handler.dispatchMessage(Handler.java:92)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.os.Looper.loop(Looper.java:130)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at android.app.ActivityThread.main(ActivityThread.java:3683)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at java.lang.reflect.Method.invokeNative(Native Method)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at java.lang.reflect.Method.invoke(Method.java:507)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
07-20 22:57:16.445: ERROR/AndroidRuntime(25843):     at dalvik.system.NativeStart.main(Native Method)
07-20 23:02:38.425: ERROR/jdwp(25924): Failed sending reply to debugger: Broken pipe

After googling for a good amount of time I came across this bug report in the Android project http://code.google.com/p/android/issues/detail?id=3114. The report’s been up for over two years now but Google hasn’t bothered to fix it (shame!). As is mentioned in the report, the problem is with the update handler variable not being set before the dialog box is shown. The solution is simple, create your own ProgressDialog without using the static factory method.

ProgressDialog progressDialog = new ProgressDialog(PlaylistExporterMain.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(false);
progressDialog.setMax(getListAdapter().getCount());
progressDialog.show();

If you have run across this bug, please go to the bug report and leave a comment, maybe it’ll force Google’s hand into fixing the bug (which requires an addition to the ProgressDialog static factory methods).

2 comments (2,348 views)
May 29th, 2011 | Tags: , , ,

I woke up today morning all charged up to work on my app again (barely slept yesterday). The fist thing I wanted to do was to add a progress dialog to my applications list activity. The activity isn’t time consuming (not much anyway) but you don’t want the UI to be unresponsive, gives a bad impression to the user.

So while the UI thread is busy on my onCreate doing stuff for me I want to show a loading dialog box. Easy right? Not really. Unless you’re an android expert (and I am not) there are a lot of pitfalls. There are things you need to work around. For example, displaying the dialog box in the UI thread, performing the activity in it and then hiding the box doesn’t work and isn’t a good idea anyway.

You must update the main UI from a separate thread. To do this I had to spawn another thread in my main onCreate method. While this does not seem ideal to me at all (I hate working with threads directly), I’m going to do this for the purposes of this example anyways.

Note: I couldn’t get the default progress dialogs to work, so we will create our own in this example. Once I get the default dislogs to work, I will put up another article.
Note2: My default activity extends ListActivity.

First, some code to create the ProgressDialog

final ProgressDialog dialog = new ProgressDialog(this);
dialog.setCancelable(true);
dialog.setMessage("Loading...");
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress(0);
dialog.setMax(100);
dialog.show();

Remeber to create the dialog before you do anything else with the UI like register for context menu create etc. If you don’t then you will get an exception.

Once we’re done with the dialog box, we go ahead and spawn our worker thread.

		Thread myThread = new Thread(new Runnable() {
			public void run() {
				try {
					TimeUnit.SECONDS.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				dialog.setProgress(100);
				dialog.dismiss();
			}
		});
		myThread.start();

See the line setting the progress to 100. This is the max progress we hve to set. If you don’t set the progress to a value equal to the max progress then the dialog box won’t go away even if you call dismiss() on it. I spent a good half hour wondering why the bar wouldn’t go away because of this.

And that’s it, your dialog box is ready. The full activity code looks like this:

public class HelloAndroid extends ListActivity {

	private List<PackageInfo> packages = new ArrayList<PackageInfo>();

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		final ProgressDialog dialog = new ProgressDialog(this);
		dialog.setCancelable(true);
		dialog.setMessage("Loading...");
		dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		dialog.setProgress(0);
		dialog.setMax(100);
		dialog.show();

		registerForContextMenu(getListView());

		Thread myThread = new Thread(new Runnable() {
			public void run() {
				try {
					TimeUnit.SECONDS.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				dialog.setProgress(100);
				dialog.dismiss();
			}
		});
		myThread.start();
		setListAdapter(new MyAdapter(this, packages));
	}
0 comments (6,716 views)
May 29th, 2011 | Tags: , , , ,

After spending two weekends reading up on the SDK and in general playing with my new Google Nexus S, I finally decided to get my feet wet. Downloading the SDK wasn’t really a breeze because the data size is large and with my crappy internet it took forever. When I finally got to writing code, I was plesantly surpised by the ADT plugin, it works flawlessly.

Anyway, I decided I wanted to list all the packages installed on the emulator in a two line list view. You’d think this would be a breeze, it was anything but.

I struggled for a while with NullPointers all over the place before I came across this example in the SDK. Despite stackoverflow, I couldn’t find what I needed on the internet and the example was what eventually helped me.

Anyway, to the example cave! Because we want our default view to be a list view our main class must extend a ListActivity instead of an Activity.

public class HelloAndroid extends ListActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

The code to retrieve all installed applications on the system is pretty straightforward.

		final PackageManager pm = getPackageManager();
		List<ApplicationInfo> packages = pm
				.getInstalledApplications(PackageManager.GET_META_DATA);

		List<ApplicationInfo> list = new ArrayList<ApplicationInfo>();
		for (ApplicationInfo packageInfo : packages) {
			list.add(packageInfo);
		}
		setListAdapter(new MyAdapter(this, list));

Next, we use the default android layout and create an adapter that extends from the BaseAdapter class. We need a custom adapter because by default the ArrayAdapter displays the toString() of objects present in the array. And we can’t use a SimpleAdapter because we don’t have a cursor. Extending the BaseAdapter allows us to customize the view to our needs and display what we want in whichever element of the view we want.

class MyAdapter extends BaseAdapter {
	private LayoutInflater mInflater;

	private List<ApplicationInfo> list;

	public MyAdapter(Context context, List<ApplicationInfo> list) {
		mInflater = LayoutInflater.from(context);
		this.list = list;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		if (convertView == null) {
			convertView = mInflater.inflate(R.layout.list_item, parent, false);
		}
		TextView title = (TextView) convertView.findViewById(R.id.item_title);
		TextView sub = (TextView) convertView.findViewById(R.id.item_subtitle);

		ApplicationInfo info = list.get(position);
		title.setText(String.valueOf(position));
		sub.setText(info.processName);
		return convertView;
	}

	public int getCount() {
		return list.size();
	}

	public Object getItem(int arg0) {
		return list.get(arg0);
	}

	public long getItemId(int arg0) {
		return arg0;
	}
}

Setting this adapter as the data provider for our list view is simple. The tricky part is making sure you get the id’s of the children view right (this one took me a while to figure out). So our final HelloAndroid class looks something like this.

public class HelloAndroid extends ListActivity {
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		final PackageManager pm = getPackageManager();
		// get a list of installed apps.
		List<ApplicationInfo> packages = pm
				.getInstalledApplications(PackageManager.GET_META_DATA);

		List<ApplicationInfo> list = new ArrayList<ApplicationInfo>();
		for (ApplicationInfo packageInfo : packages) {
			Log.d("TAG", "Installed package :" + packageInfo.processName);
			Log.d("TAG", "Launch Activity :" + packageInfo.className);
			list.add(packageInfo);
		}
		setListAdapter(new MyAdapter(this, list));
	}
}

This will display all the installed packages on your device in a neat list view. This code isn’t efficient because we query the packages in the UI threads. The ideal solution would be fire this off as an Intent and then query packages in the intent, displaying them and notifying the view that the data has changed. The use of the default ListActivity however does allow for auto-scrolling loading. This means that only what is visible on the screen is shown at the beginning and as and when you scroll down, more and more elements are rendered. This is very efficient from a UI perspective escpecially when the UI is running on a device which has limited resources (1GB RAM can run out pretty quickly).

I’ll be writing more on how we can fire off an Intent and then loa

0 comments (2,663 views)
May 28th, 2011 | Tags: , , ,

I blogged about reading a jar’s manifest file recently but what use is reading a manifest unless it contains something useful. In a development environment where we are constantly pushing SNAPSHOT’s to others, it helps to know what version of your code base others are using in order to debug problems.

Two properties here are especially useful, the version of your jar and the time it was built. To include these through maven, you must use the maven jar plugin to add the properties to your jar’s manifest at build time.

The configuration is simple, in the build section of your pom.xml add the following plugin:

			&lt;plugin&gt;
				&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
				&lt;artifactId&gt;maven-jar-plugin&lt;/artifactId&gt;
				&lt;version&gt;2.1&lt;/version&gt;
				&lt;configuration&gt;
					&lt;archive&gt;
						&lt;addMavenDescriptor /&gt;
						&lt;manifest&gt;
							&lt;addDefaultImplementationEntries /&gt;
							&lt;addDefaultSpecificationEntries /&gt;
						&lt;/manifest&gt;
						&lt;manifestEntries&gt;
							&lt;ComponentVersion&gt;${project.version}&lt;/ComponentVersion&gt;
							&lt;BuildTime&gt;${maven.build.timestamp}&lt;/BuildTime&gt;
						&lt;/manifestEntries&gt;
					&lt;/archive&gt;
				&lt;/configuration&gt;
			&lt;/plugin&gt;

And that’s it. You’re done.

I believe it would also be useful to add the revision number of the build but I haven’t quite figured out how to do that yet, so that’s for another time.

0 comments (898 views)
May 28th, 2011 | Tags: ,

I just started developing an app to try my hand at Android SDK. The first problem I ran across was that the HellowWorld android app part of the exercise on the SDK site wouldn’t deploy.

I kept getting, “Failed to install HelloAndroid.apk on device ‘emulator-5554! “. And I kept restarting the emulator. This was a big mistake. After a lot of googling, it turns out that the emulator takes a while to load and when it loads it displays the phone’s UI and not the words “android”. If you restart your emulator then it’s going to go through the whole booteup process again.

It’s a bit weird that there is no clear indication of when the emulator is fully up and running but I generally wait till I see the screen appear till I start to deploy any applications.
Running emulator

Also, you might want to increase the RAM on your AVD image to 1024. It seems that it boots up faster when it has more RAM but I have no proof to share so I can only say try it, might just work for you too.

0 comments (975 views)
May 26th, 2011 | Tags:

At work, we have multiple libraries that provide us with small small functionalities. This design keeps things simple and makes code sharing easy. But it also has it’s own set of problems. In very large projects where there are multiple branches and complicated dependencies, it’s impossible to ask customers to provide you with jar versions. The easiest way to is to have this version saved somewhere and then to print it in logs automatically.

Where do you save this version though? A file is a good choice but it’s not the right choice when things like a MANIFEST that’s supposed to hold meta information. A manifest is what we chose as well (especially because editing files as part of the release process, using the maven release plugin, isn’t supposed to work).

The primary task for us was that we had many such modules which wanted to print their version numbers. We would have to identify the exact manifest file to read and then print it’s version. As simple as this may sound, the JDK provides no help with this at all. The code, once you see it, will looked like someone hacked up a cat and put it within curly braces.

Before I list any code, I’d like to give a shout out to the folks at stackoverflow.com for the community and their work. I found what I was looking for there in this thread. Without stackoverflow’s community, I wouldn’t be able to find half the things that I am looking for because let’s face it, Google isn’t really Google anymore.

The simplest piece of code that I could write to extract a manifest file from a specified class file:

        Class clazz = this.getClass();
        String className = clazz.getSimpleName() + ".class";
        String classPath = clazz.getResource(className).toString();
        if (!classPath.startsWith("jar")) {
            // Class not from JAR
            return null;
        }
        String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
        Manifest manifest = null;
        try {
            manifest = new Manifest(new URL(manifestPath).openStream());
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }

The main aim is still not achieved though, we want any and all classes to be able to do this. So any calling class must be able to retrieve it’s own manifest file easily. This means that the above code needs to be changed slightly to that it can accept a class externally rather than using “this”. And also that the caller is passed implicitly.

To obtain the caller:

        Class<?> caller = null;
        try {
            caller = Class.forName(new Throwable().fillInStackTrace().getStackTrace()[1].getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace(System.err);
        }

And pass this “caller” to the code above. You have your manifest to read from now.

0 comments (1,265 views)
November 19th, 2010 | Tags: ,

I’ve been playing around with Griffon lately and I must say I am very impressed. The more I use it, the more I go, “Why didn’t someone bring mvc development to Swing before?” Perhaps they did. I wasn’t rally paying attention to Swing for the last 5 years or so because I there was just way too much noise in swing applications. I could go on with this rant but …

Anyway, Griffon simplifies a lot of things. One of them is setting the look and feel of your application. No one is very impresses with the default java swing app LAF so I decide to see where Substance is these days and installed the substance plugin. That’s another nice feature of Griffon, plugins, no more dealing with jar’s and configuration files to get your hands dirty. That automatically adds the substance jar to my classpath.

Next on the list of things was the LookAndFeel plugin. Easy enough. Now to make sure that when my app starts up the substance LAF is used by default. For that open the Config.groovy file in you griffon-app/conf folder and add the following lines to it:

lookandfeel {
	lookAndFeel = 'Substance'
	theme = 'Business'
}

And that’s it. None of that messy UIManager stuff and exception catching!

0 comments (1,760 views)
October 6th, 2010 | Tags: , , ,

I was playing around with Groovy’s SwingBuilder and decided to use the spinner for some odd reason. I couldn’t find a groovy implementation of a cyclic spinner so I wrote my own.

import javax.swing.*
import groovy.swing.SwingBuilder

class MyModel extends SpinnerNumberModel {
    MyModel(int value, int min, int max, int step) {super(value, min, max, step)}
    Object getNextValue() { if (value == maximum) { return minimum } else { return value + stepSize; }}
    Object getPreviousValue() { if (value == minimum) { return maximum } else { return value - stepSize; }}
}
def swing = new SwingBuilder()
swing.frame(title: 'Cyclic Spinner', defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE,
    size: [200, 60], show: true, locationRelativeTo: null) {
    lookAndFeel("system")
    SpinnerNumberModel sModel = new MyModel(0, 0, 5, 1)
    spinner1 = spinner(size:[10, 10], model: sModel)
}

Just fire up groovy console and run this code to see how it works. Suggestions are always welcome especially in this case since i’m a groovy newbie and i’m sure there’s an easier way to do this.

0 comments (2,190 views)
September 3rd, 2010 | Tags: ,

The more I work with groovy the more I like it. While my love for groovy is best left for another post, I did spend quite a bit of time figuring out how to copy files from one directory to another using groovy’s build in AntBuilder. I figured i’d post my list of AntBuilder tsks here.

First, the simplest of them (something you’ll find all over the web), deleting an entire directory:

new AntBuilder().delete(dir: "D:/tmp/test")

Then slightly more useful, copying contents of an entire directory to another directory:

new AntBuilder().copy(todir: "E:/2") {
    fileset(dir : "E:/1")
}

Notice how seamless the snippet looks. The part that I like the most is that it’s extremely expressive, any novice starting out with groovy can understand what it means (with a bit of reading up on closures).

Ofcourse ant’s copy task is a lot more powerful than just the above snippet. We can use patterns to specify what should be copied. For example, copy all java files from one directory to another:

new AntBuilder().copy(todir: "E:/2") {
    fileset(dir : "E:/1") {
        include(name:"**/*.java")
    }
}

Anoter usage is to copy all java files except for test cases:

new AntBuilder().copy(todir: "E:/2") {
    fileset(dir : "E:/1") {
        include(name:"**/*.java")
        exclude(name:"**/*Test.java")
    }
}

I have to admit, I have never used ant before so I do not know what else you can do with it but combined with groovy it looks like i’ll be using it quite bit.

0 comments (4,897 views)
November 27th, 2009 | Tags:

There’s no doubt that Joda time is now the defacto date time library for java. And a well written library it is. You get almost everything you can ever want to do with Date objects and best of all, almost all operations are dummy-proof. By dummy proof I mean that programmers making silly mistakes can’t really screw it up.

I recently had to parse a String into a LocalDate object using Joda time. I couldn’t find a parser in the docs or the very useful userguide but I knew it had to be possible, no one could overlook such a widely used operation. So I went in search of a parse.

It turns out that the parser and the formatter are one and the same thing in joda time, except with different methods ofcourse. Here’s how you construct a parser to parse a String in YYYYMMDD format :

DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder().appendYear(4, 4).appendMonthOfYear(2)
            .appendDayOfMonth(2).toFormatter();

Read more…

5 comments (10,543 views)
November 22nd, 2009 | Tags: ,

Over the last couple of years I have had various discussions about this with my colleagues. I myself favoured foreign keys relationships rather than join tables till a little while ago. If you’re not aware of how to do one or the other, the eamples below should explain that.

Consider a very simple parent child relationship. Every parent can have multiple children. Using a join table and JPA annotations, the model classes look something like this for unidirectional relationships: (I have excluded all fields not required for this example)
With join table …

@Entity
class Parent {
    @OneToMany
    @JoinTable(
            name="parents_children",
            joinColumns = @JoinColumn( name="parent_id"),
            inverseJoinColumns = @JoinColumn( name="child_id")
    )
    private Set<Child> children
}

@Entity
class Child {
    //nothing
}

Without join table …

@Entity
class Parent {
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @JoinColumn(name="parent_id")
    private Set<Child> children;
}

@Entity
class Child {
    // nothing
}

Read more…

1 comment (7,051 views)
November 18th, 2009 | Tags: ,

I had an interesting discussion with a colleague of mine regarding the differences between the proxy and the decorator pattern which made me give it some thought. To the untrained eye (that includes my eye!), they seem exactly the same. Infact you sometimes wonder, why the heck are there two names for the same pattern. As we’ll soon see, they are anything but the same.

I think we’re all aware that the decorator pattern is used to add behavior to existing code. This does not stop at one kind of behavior but any number of different things that we want to do. A proxy on the other hand simply delegates all calls to the underlying object delaying costly operations till they are absolutely neccessary. This basically means that what a proxy can do is decided at compile time and cannot be changed by the calling code after instantiation. Using the decorator pattern the behavior of the underlying object can be changed at runtime by adding multiple decorators to it. This behavior addition takes place at runtime depending on say user input. To put it simply, proxy is compile time, decorator is runtime.
Read more…

4 comments (6,305 views)
October 14th, 2009 | Tags:

I’m a bit ashamed to write this post. I have been working on Java 5 for well over two years and yet I was unaware of the power of the java.util.concurrent.TimeUnit class. While I have used almost all the other juc classes over this time and subsequently TimeUnit as well (in some of them), I never realized how powerful TimeUnit was on its own.

It all started with my obsession to remove all the checkstyle errors in a project that I have been working on. More than 50% of them turned out to be ‘magic number’ warnings. While some of them were my fault, most of them were due to laziness and then there were some due to Thread.sleep calls in the code. There must be an easy way to get rid of these without actually having to make private static final class members for every sleep value. I looked and looked and looked, nope, nothing.
Read more…

0 comments (9,693 views)
April 13th, 2009 | Tags: , ,

What i’m about to show you is not really much of a tutorial but more of an example of one way of working with eclipse and deploying your seam based web app to tomcat. If you’re ok with JBoss, then go with it, nothing beats the support and ease of configuration that the JBoss AS provides for deploying seam applications. But since I was running JBoss on an old 5400 rpm hard disk with 25 other applications reading and writing data to it, it used to take 10+ minutes for it to startup (7m with reduced logging) and anywhere from 4 to 10 minutes for a hot redeploy of the application. Maybe I wasn’t doing it right but I decided to switch to tomcat for it’s ease of use and because i’m more familiar with its errors and pitfalls (configuring a ds comes to mind).

There are a few good tutorials out there if you want to work with JBoss and maybe one or two good ones for tomcat but nothing that works out of the box. One special tutorial is Read more…

4 comments (11,149 views)