Статьи

Использование платформы NetBeans на сервере с Wicket на клиенте


Предположим, что вы разработали довольно сложное настольное приложение на основе платформы NetBeans, а позже вы обнаружите, что вы можете использовать много вещей для проекта другого клиента, на этот раз обычного веб-приложения. Ну что делать? Вы не можете использовать платформу NetBeans для создания компонентного серверного приложения, верно? Похоже, вам нужно разобрать ваше настольное приложение, извлечь .jars из модулей NetBeans .nbm (то, что я неофициально называю «выравнивание» платформы) и так далее. Конечно, вы, возможно, повторно используете некоторый код, но полностью теряете преимущества компонентной архитектуры. Правильно?

Неправильно . Вы можете использовать платформу NetBeans для создания приложения на стороне сервера! Ваши компоненты просто остаются прежними (конечно, если вы правильно отделили модели и контроллеры от GUI), и вы можете просто повторно использовать двоичные файлы (люди OSGi, вероятно, знакомы с этим, особенно с тех пор, когда Glassfish его поддерживает — новость заключается в том, что Платформа NetBeans также может быть использована).

Действительно, Ярослав Тулач уже говорил два года назад о платформе NetBeans на стороне сервера (черт возьми, есть ли в мире что-то, чего этот парень еще не сделал?) С названием проекта «DVB Central» . Как писал Тим Будро , DVB central — это «голая» платформа (в основном API BootStrap, Startup, File System, Utilities и Module System), лишенная любой части Swing, которая содержит небольшой HTTP-сервер, используемый для удаленного администрирования.

Мои потребности (потому что — как вы уже догадались — я говорю о реальном сценарии здесь) более сложны: мне нужно повторно использовать больше модулей платформы NetBeans (например, DataObjects); приложение должно быть не «автономным приложением, встраивающим HTTP-сервер», а обычным веб-приложением, которое можно развернуть как .war в Tomcat; и мне также нужно использовать для этого веб-фреймворк (а именно, Wicket ). Кроме того, приложению необходимо предоставить интерфейс WebDAV и некоторые модули REST.

Несмотря на то, что для этого потребовалось некоторое время, в настоящее время отполированное решение чище, чем я ожидал, и я собираюсь показать вам, как оно работает. Проект, который строится на нем, — это blueOcean, новый член семейства blueMarine. По сути, это blueMarine «на стороне сервера» с модулями без графического интерфейса, которые реализуют систему каталогизации. Хотя это еще не полностью доступно как законченное приложение (это скорее базовая платформа, которую я использую для создания приложения клиента с пользовательским интерфейсом), оно доступно через один или два дня на blueocean.dev.java.net и вы можете посмотреть на него, чтобы увидеть технические решения, которые я собираюсь проиллюстрировать (и через несколько недель сайт blueocean.tidalwave.itдолжен родиться). Помните, что в этой статье предполагается, что у вас есть некоторые базовые знания о развертывании приложений .war, а также о том, как создаются приложения на платформе NetBeans.

Шаг 1: Подготовьте модули обертки

Так как вы собираетесь полностью разработать веб-приложение как компонентное приложение на платформе NetBeans, все необходимые библиотеки и инфраструктуры должны быть размещены в оболочках модулей NetBeans. Вот те, которые я подготовил для blueOcean (вы можете увидеть их в папке Libraries):

  1. ServletAPI (servlet-api.jar) для основных веб-функций.
  2. Wicket и WicketExtensions для структуры Wicket .
  3. JSR311, Джерси и Джеттисон для функций REST (плюс поддержка JSON).
  4. Милтон для интерфейса WebDAV.
  5. Библиотеки, необходимые для перечисленных выше вещей (например, SLF4J, ASM, GlazedLists, Apache Commons Fileupload и т. Д., Как указано в каждой документации фреймворка).

Будьте особенно осторожны на шаге № 5, то есть убедитесь, что все зависимости выполнены (например, модуль Wicket объявляет зависимость от SLF4J, Джерси на JSR311 и ASM и т. Д.), В противном случае вы получите некоторый ClassNotFound исключения / ошибки, которые сложно понять (они относятся к классам, которые на самом деле находятся в пути к классам, но имеют отсутствующие зависимости).

По причинам, которые будут объяснены в ближайшее время, вам понадобится дополнительная вещь для каждой среды: любой сервлет или фильтр, относящийся к среде (обычно один для среды, описанной в их документации), должен быть зарегистрирован как сервис META-INF. Для этого вам нужно создать каталог META-INF / services со следующими файлами (для Wicket, Jersey и Milton):

org.apache.wicket.protocol.http.WicketFilter
com.bradmcevoy.http.MiltonServlet 
com.sun.jersey.spi.container.servlet.ServletContainer 

Каждый файл должен состоять из одной строки, содержащей тот же текст, что и имя файла. 

 

Шаг 2: Разработка вашего приложения

Теперь вы можете создать свое приложение как обычно, используя необходимые API-интерфейсы. То есть для создания пользовательского интерфейса вы создаете новые модули, которые объявляют зависимость от Servlet API и Wicket, и так далее. Потенциальный источник проблем — где разместить HTML-файлы для пользовательского интерфейса: с Wicket (который мне нравится по многим причинам, включая тот, о котором я вам сейчас говорю), они могут оставаться в той же папке, что и исходники .java, и упаковываться. в двоичные файлы Java, так как они будут доступны во время выполнения как ресурсы. Я не исследовал другие сценарии, например JSP, где веб-страницы должны быть размещены в определенном каталоге, отдельном от кода.

 

Шаг 3. Создание оболочки для веб-приложений

Теперь самая интересная часть. Вам нужно создать «клей» между Tomcat и вашим приложением на платформе; и так как я люблю делать вещи стандартным способом, мне нравится, когда этот клей принимает форму обычного .war-приложения, которое можно развернуть как обычно. Помните, что у вас есть много вариантов: .war может быть всеобъемлющим, то есть включать ваше приложение и платформу или просто указывать на платформу, которая находится в каком-то другом месте. Например, последнее решение позволило бы добавлять и удалять модули с помощью центров обновлений NetBeans, а не развертывать новый .war. Тебе решать. В качестве отправной точки, а также потому, что в данный момент мне не нужны центры обновлений, я пошел по пути всеохватывающего файла .war.

Суть «склеивания» заключается в загрузчиках классов: и у Tomcat, и у платформы есть свои загрузчики классов. Как их соединить? К счастью, интерфейс между Tomcat и веб-приложением состоит в основном только из двух вещей: сервлетов и / или фильтров. Большинству веб-фреймворков просто необходимо объявить сервлет или фильтр в файле web.xml, и эти компоненты будут действовать как один «фронт-контроллер» из фреймворка.

Таким образом, мое решение — предоставить пару универсальных декораторов (сервлет и фильтр), которые создают мост между двумя мирами. Я поместил их в очень маленькую библиотеку JSE, которую, как вы увидите позже, просто необходимо включить в проект .war и настроить (источники можно проверить по адресу https://blueocean.dev.java. net / svn / blueocean / trunk / src / NetBeansPlatformWebAdapter , редакция 11).

Это сервлет декоратор:

package it.tidalwave.netbeans.servlet;

import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.openide.util.Lookup;

public class NetBeansPlatformDecoratorServlet implements Servlet
{
private ServletConfig config;
private Servlet delegate;

public void init (final ServletConfig config)
throws ServletException
{
try
{
NetBeansPlatformUtils.boot(config.getServletContext());
}
catch (Exception e)
{
throw new ServletException(e);
}

final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
this.config = config;
final String className = config.getInitParameter("netbeans.servlet.delegate");
final Class delegateClass = Thread.currentThread().getContextClassLoader().loadClass(className);
delegate = (Servlet)Lookup.getDefault().lookup(delegateClass);

if (delegate == null)
{
throw new RuntimeException("Can't lookup " + className);
}

delegate.init(config);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public ServletConfig getServletConfig()
{
return delegate.getServletConfig();
}

public void service (final ServletRequest req, final ServletResponse res)
throws ServletException, IOException
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.service(req, res);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public String getServletInfo()
{
return delegate.getServletInfo();
}

public void destroy()
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.destroy();
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}
}

Это основные понятия:

  1. Как обычно для декораторов, поведение передается делегату.
  2. Украшение заключается в установке загрузчика класса контекста (Thread.currentThread (). GetClassLoader ()) из платформы NetBeans и восстановлении его в конце вызова метода (заботясь об использовании блока finally для безопасности). Детали реализованы в классе NetBeansPlatformUtils, обсуждаемом ниже.
  3. Экземпляр делегата извлекается путем запроса его к классу Lookup, как обычно для приложений на платформе NetBeans (именно поэтому вам пришлось объявить все соответствующие сервлеты и фильтры в META-INF / services, как описано ранее). Хотя общая идиома заключается в передаче литерала класса в качестве аргумента (например, MyService s = Lookup.getDefault (). Lookup (MyService.class)), здесь мы не можем сделать это по двум причинам: во-первых, потому что мы хотим, чтобы код быть универсальным, поэтому имя класса должно быть передано в параметре («netbeans.servlet.delegate»); и во-вторых, потому что мы не могли получить литерал каким-либо образом, так как он никогда не был бы доступен в classpath статической компиляции (нет хороших способов, чтобы .war статически включал материал Platform; если вы пытались извлечь его. JAR-файлы из.Модули nbm — которые сами по себе являются громоздким подходом — вы столкнетесь с возможными исключениями ClassCastException, поскольку у вас будет один и тот же класс, доступный как в загрузчиках классов Tomcat, так и в платформе Platform — помните, что один и тот же байт-код в двух разных загрузчиках классов обрабатывается как два разных класса).
  4. И последнее, но не менее важное: при первом вызове делегата сервлета или фильтра необходимо инициализировать платформу: это задача NetBeansPlatformUtils.boot (). 

Для полноты, это код NetBeansPlatformDecoratorFilter, определенно похожий на сервлет:

package it.tidalwave.netbeans.servlet;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.openide.util.Lookup;

public class NetBeansPlatformDecoratorFilter implements Filter
{
private Filter delegate;

public void init (final FilterConfig config)
throws ServletException
{
try
{
NetBeansPlatformUtils.boot(config.getServletContext());
}
catch (Exception e)
{
throw new ServletException(e);
}

final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
final String className = config.getInitParameter("netbeans.filter.delegate");
final Class delegateClass = Thread.currentThread().getContextClassLoader().loadClass(className);
delegate = (Filter)Lookup.getDefault().lookup(delegateClass);

if (delegate == null)
{
throw new RuntimeException("Can't lookup " + className);
}

delegate.init(config);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public void doFilter (final ServletRequest request,
final ServletResponse response,
final FilterChain chain)
throws IOException, ServletException
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.doFilter(request, response, chain);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public void destroy()
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.destroy();
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}
}

Теперь давайте посмотрим на класс NetBeansPlatformUtils. Во-первых, метод установки загрузчика класса Platform:

    private final static URL[] NO_URLS = new URL[0];
public static ClassLoader installPlatformClassLoader()
{
final ClassLoader classLoaderSave = new URLClassLoader(NO_URLS, Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(Lookup.getDefault().lookup(ClassLoader.class));

return classLoaderSave;
}

Единственное примечание, о котором стоит упомянуть, это то, что загрузчик класса Platform заключен в URLClassLoader: я столкнулся с некоторыми исключениями ClassCastException от средства JSP Tomcat, которое ожидает, что соответствующий загрузчик классов будет основан на URL. Хотя мне не интересны JSP (и я заметил, что исключения были безвредны для моего приложения), чем меньше неясных исключений во время выполнения, тем лучше.

Затем метод boot (), который просто имитирует необходимые части инициализации, выполняемые средством запуска NetBeans:

    private static volatile boolean initialized;

public synchronized static void boot (final ServletContext servletContext)
throws Exception
{
if (!initialized)
{
initialized = true;
final String fallbackNetBeansHome = servletContext.getInitParameter("netBeansHome");
final String fallbackNetBeansUser = servletContext.getInitParameter("netBeansUserDir");
final String netBeansHome = resolveProperties(System.getProperty("netbeans.home", fallbackNetBeansHome));
final String netBeansUser = resolveProperties(System.getProperty("netbeans.user", fallbackNetBeansUser));
final String netBeansDirs = findClusters(netBeansHome);

System.setProperty("netbeans.osenv.nullsep", "false");
System.setProperty("netbeans.home", netBeansHome);
System.setProperty("netbeans.dirs", netBeansDirs);
System.setProperty("netbeans.logger.console", "true");
System.setProperty("netbeans.user", netBeansUser);

new File(netBeansHome + "/config").mkdirs();
new File(netBeansUser).getParentFile().mkdirs();

org.netbeans.Main.main(new String[] { "--nosplash", "--userdir", netBeansUser });
}

Стоит упомянуть аргумент «—nosplash», который предотвращает появление всплывающего окна, показывающего процесс инициализации при запуске. Свойство netbeans.dirs должно указывать на все каталоги, в которых был подготовлен кластер (набор связанных модулей); вы можете статически настроить это свойство с помощью файла свойств, хотя я предпочитаю, чтобы оно обнаруживалось во время выполнения (это вопрос вкуса — я предпочел этот подход и для blueMarine, у которого есть собственный модуль запуска):

    private static String findClusters (final String netBeansHome)
throws IOException
{
final StringBuilder clusters = new StringBuilder();
final File[] files = new File(netBeansHome).listFiles();

if (files == null)
{
throw new IllegalArgumentException("Platform directory empty: " + netBeansHome);
}

for (final File file : files)
{
if (file.isDirectory())
{
final File updateTrackingFolder = new File(file, "update_tracking");

if (updateTrackingFolder.exists())
{
// platform* is already added as the main directory
// if (!file.getName().startsWith("platform"))
{
if (clusters.length() > 0)
{
clusters.append(File.pathSeparator);
}

clusters.append(file.getCanonicalPath());
}
}
}
}

final String buildDir = System.getProperty("netbeans.build.dir");

if (buildDir != null)
{
clusters.append(File.pathSeparator);
clusters.append(buildDir);
}

return clusters.toString();
}

Загрузочный код также должен знать о двух каталогах, указанных в качестве параметров:

  1. «netBeansHome»: где установлена ​​среда выполнения платформы (корень всех кластеров)
  2. «netBeansUserDir»: где будут размещаться файлы конфигурации платформы (и, в конечном итоге, приложения).

Для удобства я реализовал простое средство макроподстановки для системных свойств — позже вы увидите, что это довольно полезно для облегчения как разработки, так и развертывания:

    private static String resolveProperties (final String string)
{
String result = string;

for (final Entry<Object, Object> entry : System.getProperties().entrySet())
{
final String propertyName = (String)entry.getKey();
final String propertyValue = (String)entry.getValue();
result = result.replace("{" + propertyName +"}", propertyValue);
}

return result;
}

 

Шаг 4. Настройка дескриптора веб-развертывания

Дескриптор веб-развертывания зависит от вас, поскольку он зависит от того, сколько и какие фреймворки вы хотите использовать в своем приложении; каждому потребуется определенная конфигурация. Вот почему код, который я только что представил, был упакован как простая библиотека .jar, а не как полноценное приложение .war. 

Например, вот как я интегрировал Wicket в blueOcean:

    <filter>
<filter-name>WicketFilter</filter-name>
<filter-class>it.tidalwave.netbeans.servlet.NetBeansPlatformDecoratorFilter</filter-class>
<init-param>
<param-name>netbeans.filter.delegate</param-name>
<param-value>org.apache.wicket.protocol.http.WicketFilter</param-value>
</init-param>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>it.tidalwave.blueocean.webapp.BlueOceanWebApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>WicketFilter</filter-name>
<url-pattern>/app/*</url-pattern>
</filter-mapping>

Как вы можете видеть, созданный экземпляр Filter является декорированным, а имя реального фильтра Wicket передается в качестве параметра; applicationClassName — это конфигурация, специфичная для Wicket, и, конечно, она тоже указана. Это имя класса, которое представляет все приложение; это часть обычного модуля Platform.

Вот как я интегрировал Milton, серверную реализацию WebDAV, основанную на фронт-контроллере сервлета:

    <servlet>
<servlet-name>MiltonServlet</servlet-name>
<servlet-class>it.tidalwave.netbeans.servlet.NetBeansPlatformDecoratorServlet</servlet-class>
<init-param>
<param-name>netbeans.servlet.delegate</param-name>
<param-value>it.tidalwave.blueocean.milton.MiltonPatchedServlet</param-value>
</init-param>
<init-param>
<param-name>resource.factory.class</param-name>
<param-value>it.tidalwave.blueocean.milton.MiltonResourceFactory</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MiltonServlet</servlet-name>
<url-pattern>/webdav/*</url-pattern>
</servlet-mapping>

В этом случае мне пришлось предоставить исправленную версию сервлета, так как Милтон, к сожалению, использует Class.forName (…) для динамического создания классов вместо использования текущего загрузчика классов (рассмотрим это как функцию ошибки / отсутствия в Milton) , resource.factory.class — это параметр конфигурации Мильтона, несколько похожий на applicationClassName в Wicket.

REST еще не используется в blueOcean, но он уже используется в проекте клиента на его основе. Вот как выглядит файл web.xml:

    <servlet>
<servlet-name>JerseyServlet</servlet-name>
<servlet-class>it.tidalwave.netbeans.servlet.NetBeansPlatformDecoratorServlet</servlet-class>
<init-param>
<param-name>netbeans.servlet.delegate</param-name>
<param-value>com.sun.jersey.spi.container.servlet.ServletContainer</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
<param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>customer.package1;customer.package2</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>JerseyServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>

ServletContainer в Джерси содержит процессор аннотаций (называемый ResourceConfig), который сканирует все классы в пути к классам и ищет аннотации, специфичные для JSR-311. К сожалению, реализация по умолчанию предполагает, что classpath будет стандартным для веб-приложений (WEB-INF / lib и т. Д.), Поэтому он не работает для интегрированной платформы. Нужно было использовать альтернативный процессор аннотаций (PackagesResourceConfig), в котором вы явно указываете имена пакетов, которые содержат аннотации JSR-311. Это приемлемый компромисс на данный момент; Ясно, что можно написать настроенный ResourceConfig, который сканирует путь к классам платформы, но мне нужно узнать больше о Джерси, прежде чем я смогу его написать. 

Наконец, web.xml должен содержать конфигурацию платформы в качестве параметров инициализации контекста:

    <context-param>
<param-name>netBeansHome</param-name>
<param-value>{catalina.base}/webapps/blueOcean/WEB-INF/lib/blueocean</param-value>
</context-param>
<context-param>
<param-name>netBeansUserDir</param-name>
<param-value>{user.home}/.blueOcean/NetBeans</param-value>
</context-param>

Как объяснено ранее, фигурные скобки используются как макроразложение, их содержимое будет заменено значением соответствующего системного свойства. Здесь стоит упомянуть две вещи:

  1. Я предполагаю, что я разверну все приложение как один .war-файл. В этом случае я распакую файлы платформы в WEB-INF / lib / blueocean, на что указывает параметр «netBeansHome». Я полагаюсь на то, что свойство «catalina.base» указывает на каталог, в котором установлен Tomcat.
  2. Если вы используете другой веб-контейнер (например, Glassfish), эту часть необходимо изменить. Кроме того, я полагаюсь на тот факт, что веб-контейнер на самом деле будет расширять файл .war на диске (они не обязаны это делать и могут работать в памяти). Сервер должен быть правильно настроен для этих требований.

Правильно упакованный, все включено .war-файл может быть создан с помощью этой цели ant:

    <target name="war" depends="build-zip">
<zip destfile="dist/blueocean.war">
<zipfileset src="WebApplicationWrapper/dist/WebApplicationWrapper.war" />
<zipfileset src="dist/blueocean.zip" prefix="WEB-INF/lib"/>
</zip>
</target>

которая опирается на целевую платформу «build-zip» для упаковки вашего приложения в полный файл .zip.

Во время разработки воссоздание всего .war и его развертывание может занять слишком много времени. В этом случае вы можете переопределить настройки web.xml с помощью этих двух системных свойств, таких как эти (наиболее практичный способ сделать это — создать новый экземпляр Tomcat в IDE NetBeans):

-Dnetbeans.home=/Users/fritz/Business/Tidalwave/Projects/blueOcean/trunk/src/lib/platform 
-Dnetbeans.build.dir=/Users/fritz/Business/Tidalwave/Projects/blueOcean/trunk/src/build/cluster

Both of them point to your working directory; the former to the base platform you’re using for developing the application (in my case, a custom platform); the latter to the «build/cluster» directory, where NetBeans prepares the binary files for your application, laid out as a valid platform.

Now you just need to deploy the .war once, and then just compile your changes, and restart the .war inside Tomcat. Probably, working on it, it could be possible to play with the class loaders so you don’t have to restart the .war every time. In any case, consider that the .war is very small, so it restarts almost immediately.

Step 5: Patch Tomcat’s Common Classloader

Some «boot» libraries of the NetBeans Platform need to be placed in Tomcat’s common class loader. They are:

-rw-rw----  1 fritz  staff  255877 Jan  2 17:35 boot.jar
-rw-rw---- 1 fritz staff 619165 Jan 2 17:35 core.jar
-rw-rw---- 1 fritz staff 563443 Jan 2 17:35 org-openide-filesystems.jar
-rw-rw---- 1 fritz staff 23335 Jan 2 17:35 org-openide-modules.jar
-rw-rw---- 1 fritz staff 625986 Jan 2 17:35 org-openide-util.jar

For instance, you can put them in a tomcat-libs directory; in this case, modify the conf/catalina.properties file so there is this line:

common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,some/path/to/tomcat-libs/*.jar

This is an annoyance — could be also a problem in some cases (e.g. providers with virtualized hosts don’t give you access to the Tomcat configuration). For sure it would be better if you could place these libraries under WEB-INF/lib, but trying that leads to very strange errors. Still investigating on this.

 

A Final Word of Wisdom

The thing works, and at least for the Wicket part I’m having it in production since a couple of months (the WebDAV and REST part are still in development). As usual, you must be very careful when dealing with class loaders, since they can lead to obscure errors when you don’t expect (for instance, it suffices that a part of the frameworks you’re using does a Class.forName(…) — see how I had to patch Milton, for instance). So, before going that way, prototype and test toroughly. Of course, if you only deal with FLOSS software, problems should be always trackable and fixable in some way. 

One should also investigate how the thing scales — for instance, being the NetBeans Platform designed primarlily for the Desktop (= single user applications), it could contain bottlenecks when used with concurrent users. Only testing can tell — so far, so good, but I’m not running yet with a high number of concurrent customers. In my scenario, I think that I know quite well the NetBeans Platform so I could tweak it if necessary, and this little risk is worth while since I’m getting a huge code reuse (in any case, I’ve got a «backup plan» for my customer in case I find some blocking issue). In other scenarios, one might want to be more careful. For sure, using the NetBeans Platform in this way is an architectural choice that needs to be explored.

It is also to be said that the NetBeans Platform contains some unnecessary hardwired dependencies on Swing, for instance if you use the DataObjects. In this case, you will need to bring into your application some Swing-related modules to resolve that dependencies, even though they will be not used. Also in this case, I didn’t experience problems, but you have to test it in our scenario.

Last but not least, it would be interesting to see how many parts of the NetBeans Platform can be effectively used in a server-side application. For instance, I’m not using Nodes — but they can be useful as well, as they associate actions (menus) to objects and could be used together with Wicket’s AJAX capabilities to generate dynamic context menus. On the other hands I fear that they, being related to the single-thread model of Swing, could be a serious bottleneck. I guess this is matter for further investigation (and a further article).

For the record, I’ve proposed that the small adapter library is added to the PlatformX project. Also, I’ve submitted a speech proposal about these topics at JavaOne 2009 — so, if you like this article, please speak loud. ?