diff --git a/celements-xapp/component/pom.xml b/celements-xapp/component/pom.xml
new file mode 100644
index 000000000..483e79e34
--- /dev/null
+++ b/celements-xapp/component/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ com.celements
+ celements
+ 6.5-SNAPSHOT
+
+ celements-xapp
+ 6.0-SNAPSHOT
+ Celements XApp
+
+
+ com.celements
+ celements-model
+ 6.6-SNAPSHOT
+ provided
+
+
+ javax.servlet
+ javax.servlet-api
+ test
+
+
+
+ scm:git:git@github.com:celements/celements-features.git
+ scm:git:git@github.com:celements/celements-features.git
+ https://github.com/celements/celements-features/celements-xapp/component
+ HEAD
+
+
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManager.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManager.java
new file mode 100644
index 000000000..cdc40fc43
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManager.java
@@ -0,0 +1,392 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.xpn.xwiki.XWiki;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.classes.ListClass;
+import com.xpn.xwiki.plugin.applicationmanager.core.plugin.XWikiPluginMessageTool;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplication;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplicationClass;
+
+/**
+ * Hidden toolkit used by the plugin API that make all the plugins actions.
+ *
+ * @version $Id$
+ */
+public final class ApplicationManager
+{
+ /**
+ * The logging tool.
+ */
+ protected static final Log LOG = LogFactory.getLog(ApplicationManager.class);
+
+ /**
+ * Wiki preferences document and class full name.
+ */
+ private static final String XWIKIPREFERENCES = "XWiki.XWikiPreferences";
+
+ /**
+ * "documentBundles" list field name of the XWiki.XWikiPreferences class.
+ */
+ private static final String XWIKIPREFERENCES_DOCUMENTBUNDLES = "documentBundles";
+
+ /**
+ * "documentBundles" list field separator of the XWiki.XWikiPreferences class.
+ */
+ private static final String XWIKIPREFERENCES_DOCUMENTBUNDLES_SEP = ",";
+
+ /**
+ * The message tool to use to generate error or comments.
+ */
+ private XWikiPluginMessageTool messageTool;
+
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param messageTool the message tool
+ */
+ public ApplicationManager(XWikiPluginMessageTool messageTool)
+ {
+ this.messageTool = messageTool;
+ }
+
+ /**
+ * Get the {@link XWikiPluginMessageTool} to use with ApplicationManager.
+ *
+ * @param context the XWiki context.
+ * @return a translated strings manager.
+ */
+ public XWikiPluginMessageTool getMessageTool(XWikiContext context)
+ {
+ return this.messageTool != null ? this.messageTool : ApplicationManagerMessageTool.getDefault(context);
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // Applications management
+
+ /**
+ * Get the current wiki root application.
+ *
+ * @param context the XWiki context.
+ * @return the root application descriptor document. If can't find root application return null.
+ * @throws XWikiException error when getting root application descriptor document from database.
+ */
+ public XWikiApplication getRootApplication(XWikiContext context) throws XWikiException
+ {
+ XWiki xwiki = context.getWiki();
+
+ String docFullName = xwiki.getXWikiPreference("rootapplication", null, context);
+
+ if (docFullName != null) {
+ XWikiDocument doc = xwiki.getDocument(docFullName, context);
+
+ if (!doc.isNew()) {
+ return XWikiApplicationClass.getInstance(context).newXObjectDocument(doc, 0, context);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Search for all document containing a object of class XWikiApplicationClass.
+ *
+ * @param context the XWiki context.
+ * @return a list if {@link XWikiApplication}.
+ * @throws XWikiException error when searching documents.
+ */
+ public List getApplicationList(XWikiContext context) throws XWikiException
+ {
+ return XWikiApplicationClass.getInstance(context, false).searchXObjectDocuments(context);
+ }
+
+ /**
+ * Create a new application descriptor base on provided application descriptor.
+ *
+ * @param userAppSuperDoc appXObjectDocument the user application descriptor from which new descriptor will be
+ * created.
+ * @param failOnExist if true fail if the application descriptor to create already exists.
+ * @param comment a comment used when saving application descriptor document.
+ * @param context the XWiki Context.
+ * @throws XWikiException error when calling for {@link XWiki#getDocument(String, XWikiContext)}
+ */
+ public void createApplication(XWikiApplication userAppSuperDoc, boolean failOnExist, String comment,
+ XWikiContext context) throws XWikiException
+ {
+ XWiki xwiki = context.getWiki();
+ XWikiApplicationClass appClass = XWikiApplicationClass.getInstance(context);
+
+ // Verify is server page already exist
+ XWikiDocument docToSave =
+ xwiki.getDocument(appClass.getItemDocumentDefaultFullName(userAppSuperDoc.getAppName(), context), context);
+
+ if (!docToSave.isNew() && appClass.isInstance(docToSave)) {
+ // If we are not allowed to continue if server page already exists
+ if (failOnExist) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error(getMessageTool(context).get(ApplicationManagerMessageTool.ERROR_APPPAGEALREADYEXISTS,
+ userAppSuperDoc.getAppName()));
+ }
+
+ throw new ApplicationManagerException(ApplicationManagerException.ERROR_AM_APPDOCALREADYEXISTS,
+ getMessageTool(context).get(ApplicationManagerMessageTool.ERROR_APPPAGEALREADYEXISTS,
+ userAppSuperDoc.getAppName()));
+ } else if (LOG.isWarnEnabled()) {
+ LOG.warn(getMessageTool(context).get(ApplicationManagerMessageTool.ERROR_APPPAGEALREADYEXISTS,
+ userAppSuperDoc.getAppName()));
+ }
+
+ }
+
+ XWikiApplication appSuperDocToSave =
+ XWikiApplicationClass.getInstance(context).newXObjectDocument(docToSave, 0, context);
+
+ appSuperDocToSave.mergeObject(userAppSuperDoc);
+
+ appSuperDocToSave.save(comment);
+
+ // Update user document with the new document name
+ userAppSuperDoc.setFullName(appSuperDocToSave.getFullName());
+ }
+
+ /**
+ * Delete an application descriptor document.
+ *
+ * @param appName the name of the application.
+ * @param context the XWiki context.
+ * @throws XWikiException error when calling for {@link XWikiApplication#delete()}
+ */
+ public void deleteApplication(String appName, XWikiContext context) throws XWikiException
+ {
+ XWikiApplication app = getApplication(appName, context, true);
+
+ app.delete();
+ }
+
+ /**
+ * Get the application descriptor document of the provided application name.
+ *
+ * @param appName the name of the application.
+ * @param context the XWiki context.
+ * @param validate indicate if it return new XWikiDocument or throw exception if application descriptor does not
+ * exist.
+ * @return the XWikiApplication representing application descriptor.
+ * @throws XWikiException error when searching for application descriptor document.
+ */
+ public XWikiApplication getApplication(String appName, XWikiContext context, boolean validate)
+ throws XWikiException
+ {
+ return XWikiApplicationClass.getInstance(context, false).getApplication(appName, validate, context);
+ }
+
+ /**
+ * Reload xwiki application. It means :
+ *
+ *
update XWikiPreferences with application translation documents.
+ *
+ *
+ * @param app the application to reload.
+ * @param comment the comment to use when saving documents.
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting wiki preferences document.
+ *
or saving wiki preferences document.
+ *
+ */
+ public void reloadApplication(XWikiApplication app, String comment, XWikiContext context) throws XWikiException
+ {
+ updateApplicationTranslation(app, comment, context);
+ }
+
+ /**
+ * Reload all xwiki applications. It means :
+ *
+ *
update XWikiPreferences with application translation documents.
+ *
+ *
+ * @param comment the comment to use when saving documents.
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting wiki preferences document.
+ *
or searching for all applications in the wiki.
+ *
or saving wiki preferences document.
+ *
+ */
+ public void reloadAllApplications(String comment, XWikiContext context) throws XWikiException
+ {
+ List applist = getApplicationList(context);
+
+ for (XWikiApplication app : applist) {
+ updateApplicationTranslation(app, comment, context);
+ }
+ }
+
+ /**
+ * Insert in XWiki.XWikiPreferences "documentBundles" field the translation documents of all applications in the
+ * context's wiki.
+ *
+ * @param applications the applications for which to update translations informations.
+ * @param comment a comment used when saving XWiki.
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting wiki preferences document.
+ *
or searching for all applications in the wiki.
+ *
or saving wiki preferences document.
+ *
+ */
+ public void updateApplicationsTranslation(Collection applications, String comment,
+ XWikiContext context) throws XWikiException
+ {
+ XWiki xwiki = context.getWiki();
+
+ XWikiDocument prefsDoc = xwiki.getDocument(XWIKIPREFERENCES, context);
+ BaseObject prefsObject = prefsDoc.getObject(XWIKIPREFERENCES);
+
+ if (prefsObject != null) {
+ String documentBundles = prefsObject.getStringValue(XWIKIPREFERENCES_DOCUMENTBUNDLES);
+ List translationPrefs =
+ ListClass.getListFromString(documentBundles, XWIKIPREFERENCES_DOCUMENTBUNDLES_SEP, true);
+
+ boolean updateprefs = false;
+
+ for (XWikiApplication app : applications) {
+ updateprefs |= updateApplicationTranslation(translationPrefs, app);
+ }
+
+ if (updateprefs) {
+ prefsObject.setStringValue(XWIKIPREFERENCES_DOCUMENTBUNDLES, StringUtils.join(
+ translationPrefs.toArray(), XWIKIPREFERENCES_DOCUMENTBUNDLES_SEP));
+ xwiki.saveDocument(prefsDoc, comment, context);
+ }
+ }
+ }
+
+ /**
+ * Insert in XWiki.XWikiPreferences "documentBundles" field the translation documents of all applications in the
+ * context's wiki.
+ *
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting wiki preferences document.
+ *
or searching for all applications in the wiki.
+ *
or saving wiki preferences document.
+ *
+ */
+ public void updateAllApplicationTranslation(XWikiContext context) throws XWikiException
+ {
+ updateApplicationsTranslation(getApplicationList(context), getMessageTool(context).get(
+ ApplicationManagerMessageTool.COMMENT_REFRESHALLTRANSLATIONS), context);
+ }
+
+ /**
+ * Insert in XWiki.XWikiPreferences "documentBundles" field the translation documents of all applications in the
+ * context's wiki.
+ *
+ * @param document the document containing the applications descriptors
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting wiki preferences document.
+ *
or searching for all applications in the wiki.
+ *
or saving wiki preferences document.
+ *
+ * @since 1.9
+ */
+ public void updateApplicationsTranslation(XWikiDocument document, XWikiContext context) throws XWikiException
+ {
+ List appList =
+ XWikiApplicationClass.getInstance(context).newXObjectDocumentList(document, context);
+ updateApplicationsTranslation(appList, getMessageTool(context).get(
+ ApplicationManagerMessageTool.COMMENT_AUTOUPDATETRANSLATIONS, document.getFullName()), context);
+ }
+
+ /**
+ * Insert in XWiki.XWikiPreferences "documentBundles" field the translation documents of the provided application.
+ *
+ * @param app the application descriptor.
+ * @param comment a comment used when saving XWiki.
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting wiki preferences document.
+ *
or saving wiki preferences document.
+ *
+ */
+ public void updateApplicationTranslation(XWikiApplication app, String comment, XWikiContext context)
+ throws XWikiException
+ {
+ XWiki xwiki = context.getWiki();
+
+ XWikiDocument prefsDoc = xwiki.getDocument(XWIKIPREFERENCES, context);
+ BaseObject prefsObject = prefsDoc.getObject(XWIKIPREFERENCES);
+
+ if (prefsObject != null) {
+ String documentBundles = prefsObject.getStringValue(XWIKIPREFERENCES_DOCUMENTBUNDLES);
+ List translationPrefs =
+ ListClass.getListFromString(documentBundles, XWIKIPREFERENCES_DOCUMENTBUNDLES_SEP, true);
+
+ boolean updateprefs = updateApplicationTranslation(translationPrefs, app);
+
+ if (updateprefs) {
+ prefsObject.setStringValue(XWIKIPREFERENCES_DOCUMENTBUNDLES, StringUtils.join(
+ translationPrefs.toArray(), XWIKIPREFERENCES_DOCUMENTBUNDLES_SEP));
+ xwiki.saveDocument(prefsDoc, comment, context);
+ }
+ }
+ }
+
+ /**
+ * Insert in translationPrefs the translation documents of the provided application.
+ *
+ * @param translationPrefs the list of translation documents to complete.
+ * @param app the application's descriptor.
+ * @return true if at least one document has been inserted in translationPrefs.
+ */
+ public boolean updateApplicationTranslation(List translationPrefs, XWikiApplication app)
+ {
+ boolean updateprefs = false;
+
+ List translationDocs = app.getTranslationDocs();
+ for (String translationDoc : translationDocs) {
+ if (!translationPrefs.contains(translationDoc)) {
+ translationPrefs.add(translationDoc);
+ updateprefs = true;
+ }
+ }
+
+ return updateprefs;
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerException.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerException.java
new file mode 100644
index 000000000..89da3f247
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerException.java
@@ -0,0 +1,109 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager;
+
+import com.xpn.xwiki.plugin.PluginException;
+
+/**
+ * Application Manager plugin base exception.
+ *
+ * @version $Id$
+ */
+public class ApplicationManagerException extends PluginException
+{
+ /**
+ * Application Manager plugin error identifier.
+ */
+ public static final int MODULE_PLUGIN_APPLICATIONMANAGER = 60;
+
+ /**
+ * Error when trying to create application descriptor that already exist in the database.
+ */
+ public static final int ERROR_AM_APPDOCALREADYEXISTS = 60010;
+
+ /**
+ * Error when trying to get application descriptor that does not exist in the database.
+ */
+ public static final int ERROR_AM_DOESNOTEXIST = 60011;
+
+ /**
+ * The default ApplicationManagerException.
+ */
+ private static final ApplicationManagerException DEFAULT_EXCEPTION = new ApplicationManagerException();
+
+ // //////
+
+ /**
+ * Create an ApplicationManagerException.
+ *
+ * @param code the error code.
+ * @param message a literal message about this error.
+ */
+ public ApplicationManagerException(int code, String message)
+ {
+ super(ApplicationManagerPlugin.class, code, message);
+ }
+
+ /**
+ * Create an ApplicationManagerException. Replace any parameters found in the message by the passed
+ * args parameters. The format is the one used by {@link java.text.MessageFormat}.
+ *
+ * @param code the error code.
+ * @param message a literal message about this error.
+ * @param e the exception this exception wrap.
+ * @param args the array of parameters to use for replacing "{N}" elements in the string. See
+ * {@link java.text.MessageFormat} for the full syntax
+ */
+ public ApplicationManagerException(int code, String message, Throwable e, Object[] args)
+ {
+ super(ApplicationManagerPlugin.class, code, message, e, args);
+ }
+
+ /**
+ * Create an ApplicationManagerException.
+ *
+ * @param code the error code.
+ * @param message a literal message about this error.
+ * @param e the exception this exception wrap.
+ */
+ public ApplicationManagerException(int code, String message, Throwable e)
+ {
+ super(ApplicationManagerPlugin.class, code, message, e);
+ }
+
+ // //////
+
+ /**
+ * Create default ApplicationManagerException.
+ */
+ private ApplicationManagerException()
+ {
+ super(ApplicationManagerPlugin.class, 0, "No error");
+ }
+
+ /**
+ * @return unique instance of the default ApplicationManagerException.
+ */
+ public static ApplicationManagerException getDefaultException()
+ {
+ return DEFAULT_EXCEPTION;
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerMessageTool.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerMessageTool.java
new file mode 100644
index 000000000..63c2a8e6e
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerMessageTool.java
@@ -0,0 +1,204 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.plugin.applicationmanager.core.plugin.XWikiPluginMessageTool;
+
+/**
+ * Application Manager plugin translation messages manager.
+ *
+ * The main use of this class is construct {@link XWikiPluginMessageTool} with the correct
+ * {@link java.util.ResourceBundle} and to list all the message keys used internally in the plugin.
+ *
+ * @version $Id$
+ * @since 1.1
+ */
+public class ApplicationManagerMessageTool extends XWikiPluginMessageTool
+{
+ /**
+ * Key to use with {@link XWikiContext#get(Object)}.
+ */
+ public static final String MESSAGETOOL_CONTEXT_KEY = "applicationmanagermessagetool";
+
+ /**
+ * Used as comment when creating a new application.
+ */
+ public static final String COMMENT_CREATEAPPLICATION = "applicationmanager.plugin.comment.createapplication";
+
+ /**
+ * Used as comment when importing a new application.
+ */
+ public static final String COMMENT_IMPORTAPPLICATION = "applicationmanager.plugin.comment.importapplication";
+
+ /**
+ * Used as comment when reloading an application.
+ */
+ public static final String COMMENT_RELOADAPPLICATION = "applicationmanager.plugin.comment.reloadapplication";
+
+ /**
+ * Used as comment when reloading all applications.
+ */
+ public static final String COMMENT_RELOADALLAPPLICATIONS =
+ "applicationmanager.plugin.comment.reloadallapplications";
+
+ /**
+ * Used as comment when automatically update application translations pages.
+ */
+ public static final String COMMENT_AUTOUPDATETRANSLATIONS =
+ "applicationmanager.plugin.comment.autoupdatetranslations";
+
+ /**
+ * Used as comment when refreshing all applications translations pages.
+ */
+ public static final String COMMENT_REFRESHALLTRANSLATIONS =
+ "applicationmanager.plugin.comment.refreshalltranslations";
+
+ /**
+ * Used as {@link ApplicationManagerException} message when application default page name already exists.
+ */
+ public static final String ERROR_APPPAGEALREADYEXISTS =
+ "applicationmanager.plugin.error.applicationpagealreadyexists";
+
+ /**
+ * Used as {@link ApplicationManagerException} message when provided XAR package does not exists.
+ */
+ public static final String ERROR_IMORT_PKGDOESNOTEXISTS =
+ "applicationmanager.plugin.error.import.packagedoesnotexists";
+
+ /**
+ * Used as {@link ApplicationManagerException} message when failed to load XAR package as list of
+ * {@link com.xpn.xwiki.doc.XWikiDocument}.
+ */
+ public static final String ERROR_IMORT_IMPORT = "applicationmanager.plugin.error.import.import";
+
+ /**
+ * Used as {@link ApplicationManagerException} message when failed to insert loaded
+ * {@link com.xpn.xwiki.doc.XWikiDocument} from package into database.
+ */
+ public static final String ERROR_IMORT_INSTALL = "applicationmanager.plugin.error.import.install";
+
+ /**
+ * Used as {@link ApplicationManagerException} message when failed to find application from provided application
+ * name.
+ */
+ public static final String ERROR_APPDOESNOTEXISTS = "applicationmanager.plugin.error.applicationdoesnotexists";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when application creation failed.
+ */
+ public static final String LOG_CREATEAPP = "applicationmanager.plugin.log.createapplication";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when application delete failed.
+ */
+ public static final String LOG_DELETEAPP = "applicationmanager.plugin.log.deleteapplication";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when getting all application descriptors failed.
+ */
+ public static final String LOG_GETALLAPPS = "applicationmanager.plugin.log.getallapplications";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when getting application descriptor failed.
+ */
+ public static final String LOG_GETAPP = "applicationmanager.plugin.log.getapplication";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when exporting application failed.
+ */
+ public static final String LOG_EXPORTAPP = "applicationmanager.plugin.log.exportapplication";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when importing application failed.
+ */
+ public static final String LOG_IMPORTAPP = "applicationmanager.plugin.log.importapplication";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when reloading application failed.
+ */
+ public static final String LOG_RELOADAPP = "applicationmanager.plugin.log.reloadapplication";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when reloading all applications failed.
+ */
+ public static final String LOG_REALOADALLAPPS = "applicationmanager.plugin.log.realoadallapplications";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when automatically updating application translations
+ * informations failed.
+ */
+ public static final String LOG_AUTOUPDATETRANSLATIONS = "applicationmanager.plugin.log.autoupdatetranslations";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when refreshing all applications translations pages.
+ */
+ public static final String LOG_REFRESHALLTRANSLATIONS = "applicationmanager.plugin.log.refreshalltranslations";
+
+ /**
+ * Used as {@link org.apache.commons.logging.Log} log message when getting wiki root application failed.
+ */
+ public static final String LOG_GETROOTAPP = "applicationmanager.plugin.log.getrootapplication";
+
+ /**
+ * Default bundle manager where to find translated messages.
+ */
+ private static final ApplicationManagerMessageTool DEFAULTMESSAGETOOL = new ApplicationManagerMessageTool();
+
+ /**
+ * Create default WikiManagerMessageTool. Only look at WikiManager properties file with system {@link Locale}.
+ */
+ private ApplicationManagerMessageTool()
+ {
+ super(ResourceBundle.getBundle(ApplicationManagerPlugin.PLUGIN_NAME + "/ApplicationResources"));
+ }
+
+ /**
+ * Call for {@link XWikiPluginMessageTool#XWikiPluginMessageTool(ResourceBundle, XWikiContext)}. Construct
+ * ResourceBundle based on {@link WikiManagerPlugin#PLUGIN_NAME} + "/ApplicationResources".
+ *
+ * @param locale the {@link Locale} used to load the {@link ResourceBundle}.
+ * @param plugin the plugin.
+ * @param context the {@link com.xpn.xwiki.XWikiContext} object, used to get access to XWiki primitives for loading
+ * documents
+ */
+ ApplicationManagerMessageTool(Locale locale, ApplicationManagerPlugin plugin, XWikiContext context)
+ {
+ super(locale, plugin, context);
+ }
+
+ /**
+ * Get Wiki Manager message tool registered in XWiki context. If not return default.
+ *
+ * @param context the XWiki context from which to get message tool.
+ * @return the default Wiki Manager message tool.
+ */
+ public static ApplicationManagerMessageTool getDefault(XWikiContext context)
+ {
+ Object messagetool = context.get(MESSAGETOOL_CONTEXT_KEY);
+
+ return messagetool != null && messagetool instanceof ApplicationManagerMessageTool
+ ? (ApplicationManagerMessageTool) messagetool : DEFAULTMESSAGETOOL;
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerPlugin.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerPlugin.java
new file mode 100644
index 000000000..170db12e4
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerPlugin.java
@@ -0,0 +1,174 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xwiki.bridge.event.DocumentCreatedEvent;
+import org.xwiki.bridge.event.DocumentUpdatedEvent;
+import org.xwiki.observation.EventListener;
+import org.xwiki.observation.ObservationManager;
+import org.xwiki.observation.event.Event;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Api;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
+import com.xpn.xwiki.plugin.XWikiPluginInterface;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplicationClass;
+import com.xpn.xwiki.web.Utils;
+import com.xpn.xwiki.web.XWikiURLFactory;
+
+/**
+ * Entry point of the Application Manager plugin.
+ *
+ * @version $Id$
+ */
+public class ApplicationManagerPlugin extends XWikiDefaultPlugin implements EventListener
+{
+ /**
+ * Identifier of Application Manager plugin.
+ */
+ public static final String PLUGIN_NAME = "applicationmanager";
+
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The logging tool.
+ */
+ protected static final Log LOG = LogFactory.getLog(ApplicationManagerPlugin.class);
+
+ /**
+ * The events matchers.
+ */
+ private static final List EVENTS = new ArrayList()
+ {
+ {
+ add(new DocumentUpdatedEvent());
+ add(new DocumentCreatedEvent());
+ }
+ };
+
+ /**
+ * Protected API for managing applications.
+ */
+ private ApplicationManager applicationManager;
+
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Construction the entry point of the Application Manager plugin.
+ *
+ * @param name the identifier of the plugin.
+ * @param className the class name of the entry point of the plugin.
+ * @param context the XWiki context.
+ */
+ public ApplicationManagerPlugin(String name, String className, XWikiContext context)
+ {
+ super(name, className, context);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.xwiki.observation.EventListener#getEvents()
+ */
+ public List getEvents()
+ {
+ return EVENTS;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#init(com.xpn.xwiki.XWikiContext)
+ */
+ @Override
+ public void init(XWikiContext context)
+ {
+ this.applicationManager = new ApplicationManager(ApplicationManagerMessageTool.getDefault(context));
+
+ // register for documents modifications events
+ Utils.getComponent(ObservationManager.class).addListener(this);
+
+ String database = context.getDatabase();
+ try {
+ XWikiURLFactory urlf =
+ context.getWiki().getURLFactoryService().createURLFactory(context.getMode(), context);
+ context.setURLFactory(urlf);
+ context.setDatabase(context.getMainXWiki());
+ this.applicationManager.updateAllApplicationTranslation(context);
+ } catch (XWikiException e) {
+ LOG.error(ApplicationManagerMessageTool.getDefault(context).get(
+ ApplicationManagerMessageTool.LOG_REFRESHALLTRANSLATIONS), e);
+ } finally {
+ context.setDatabase(database);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.xwiki.observation.EventListener#onEvent(org.xwiki.observation.event.Event, java.lang.Object,
+ * java.lang.Object)
+ */
+ public void onEvent(Event event, Object source, Object data)
+ {
+ XWikiDocument document = (XWikiDocument) source;
+ XWikiContext context = (XWikiContext) data;
+
+ try {
+ if (XWikiApplicationClass.isApplication(document)) {
+ this.applicationManager.updateApplicationsTranslation(document, context);
+ }
+ } catch (XWikiException e) {
+ LOG.error(ApplicationManagerMessageTool.getDefault(context).get(
+ ApplicationManagerMessageTool.LOG_AUTOUPDATETRANSLATIONS, document.getFullName()), e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#getName()
+ */
+ @Override
+ public String getName()
+ {
+ return PLUGIN_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#getPluginApi(com.xpn.xwiki.plugin.XWikiPluginInterface,
+ * com.xpn.xwiki.XWikiContext)
+ */
+ @Override
+ public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context)
+ {
+ return new ApplicationManagerPluginApi((ApplicationManagerPlugin) plugin, context);
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerPluginApi.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerPluginApi.java
new file mode 100644
index 000000000..4e3a6f2b9
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationManagerPluginApi.java
@@ -0,0 +1,439 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import com.xpn.xwiki.plugin.applicationmanager.core.api.XWikiExceptionApi;
+import com.xpn.xwiki.plugin.PluginApi;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplication;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplicationClass;
+import com.xpn.xwiki.web.XWikiMessageTool;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Plugin for managing applications: installation, export, creation. The plugin uses the concept of an Application
+ * Descriptor describing an application (its version, the documents it contains, the translations, etc).
+ *
+ * @version $Id$
+ * @see com.xpn.xwiki.plugin.applicationmanager.ApplicationManagerPlugin
+ */
+public class ApplicationManagerPluginApi extends PluginApi
+{
+ /**
+ * Field name of the last error code inserted in context.
+ */
+ public static final String CONTEXT_LASTERRORCODE = "lasterrorcode";
+
+ /**
+ * Field name of the last api exception inserted in context.
+ */
+ public static final String CONTEXT_LASTEXCEPTION = "lastexception";
+
+ /**
+ * The logging tool.
+ */
+ protected static final Log LOG = LogFactory.getLog(ApplicationManagerPluginApi.class);
+
+ /**
+ * The default ApplicationManager managed exception.
+ */
+ private XWikiExceptionApi defaultException;
+
+ /**
+ * Protected API for managing applications.
+ */
+ private ApplicationManager applicationManager;
+
+ /**
+ * Protected API for installing/exporting applications.
+ */
+ private ApplicationPackager applicationPackager;
+
+ /**
+ * The plugin internationalization service.
+ */
+ private ApplicationManagerMessageTool messageTool;
+
+ /**
+ * Create an instance of the Application Manager plugin user api.
+ *
+ * @param plugin the entry point of the Application Manager plugin.
+ * @param context the XWiki context.
+ */
+ public ApplicationManagerPluginApi(ApplicationManagerPlugin plugin, XWikiContext context)
+ {
+ super(plugin, context);
+
+ // Default Exception
+ this.defaultException = new XWikiExceptionApi(ApplicationManagerException.getDefaultException(), context);
+
+ // Message Tool
+ Locale locale = (Locale) context.get("locale");
+ this.messageTool = new ApplicationManagerMessageTool(locale, plugin, context);
+ context.put(ApplicationManagerMessageTool.MESSAGETOOL_CONTEXT_KEY, this.messageTool);
+
+ this.applicationManager = new ApplicationManager(this.messageTool);
+ this.applicationPackager = new ApplicationPackager(this.messageTool);
+ }
+
+ /**
+ * @return the default plugin api exception.
+ */
+ public XWikiExceptionApi getDefaultException()
+ {
+ return this.defaultException;
+ }
+
+ /**
+ * @return the plugin internationalization service.
+ */
+ public XWikiMessageTool getMessageTool()
+ {
+ return this.messageTool;
+ }
+
+ /**
+ * Log error and store details in the context.
+ *
+ * @param errorMessage error message.
+ * @param e the catched exception.
+ */
+ public void logError(String errorMessage, XWikiException e)
+ {
+ LOG.error(errorMessage, e);
+
+ context.put(CONTEXT_LASTERRORCODE, Integer.valueOf(e.getCode()));
+ context.put(CONTEXT_LASTEXCEPTION, new XWikiExceptionApi(e, context));
+ }
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // Applications management
+
+ /**
+ * Create empty application document.
+ *
+ * @return an empty application descriptor document.
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public XWikiApplication createApplicationDocument() throws XWikiException
+ {
+ return XWikiApplicationClass.getInstance(context).newXObjectDocument(context);
+ }
+
+ /**
+ * Create a new application descriptor base on provided application descriptor.
+ *
+ * @param appXObjectDocument the user application descriptor from which new descriptor will be created.
+ * @param failOnExist if true fail if the application descriptor to create already exists.
+ * @return error code . If there is error, it add error code in context {@link #CONTEXT_LASTERRORCODE} field and
+ * exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ *
+ * Error codes can be :
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : method succeed with no error.
+ *
{@link XWikiException#ERROR_XWIKI_ACCESS_DENIED} : context's user don't have rights to do this
+ * action.
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public int createApplication(XWikiApplication appXObjectDocument, boolean failOnExist) throws XWikiException
+ {
+ int returncode = XWikiExceptionApi.ERROR_NOERROR;
+
+ try {
+ this.applicationManager.createApplication(appXObjectDocument, failOnExist, this.messageTool.get(
+ ApplicationManagerMessageTool.COMMENT_CREATEAPPLICATION, appXObjectDocument.toString()), context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_CREATEAPP, appXObjectDocument.toString()),
+ e);
+
+ returncode = e.getCode();
+ }
+
+ return returncode;
+ }
+
+ /**
+ * Delete an application descriptor document.
+ *
+ * @param appName the name of the application.
+ * @return error code . If there is error, it add error code in context {@link #CONTEXT_LASTERRORCODE} field and
+ * exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ *
+ * Error codes can be :
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : action finished with no error.
+ *
{@link XWikiException#ERROR_XWIKI_ACCESS_DENIED} : context's user don't have rights to do this
+ * action.
+ *
{@link ApplicationManagerException#ERROR_AM_DOESNOTEXIST} : provided application does not exist.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public int deleteApplication(String appName) throws XWikiException
+ {
+ int returncode = XWikiExceptionApi.ERROR_NOERROR;
+
+ try {
+ this.applicationManager.deleteApplication(appName, context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_DELETEAPP, appName), e);
+
+ returncode = e.getCode();
+ }
+
+ return returncode;
+ }
+
+ /**
+ * Get all applications descriptors documents.
+ *
+ * @return a list of XWikiApplication.
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public List getApplicationDocumentList() throws XWikiException
+ {
+ List listDocument = Collections.emptyList();
+
+ try {
+ listDocument = this.applicationManager.getApplicationList(this.context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_GETALLAPPS), e);
+ }
+
+ return listDocument;
+ }
+
+ /**
+ * Get the application descriptor document of the provided application name.
+ *
+ * @param appName the name of the application.
+ * @return the application descriptor document. If there is error, it add error code in context
+ * {@link #CONTEXT_LASTERRORCODE} field and exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ * Error codes can be :
+ *
+ *
{@link ApplicationManagerException#ERROR_AM_DOESNOTEXIST} : provided application does not exist.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public XWikiApplication getApplicationDocument(String appName) throws XWikiException
+ {
+ XWikiApplication app = null;
+
+ try {
+ app = this.applicationManager.getApplication(appName, context, true);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_GETAPP, appName), e);
+ }
+
+ return app;
+ }
+
+ /**
+ * Export an application into XAR using Packaging plugin.
+ *
+ * @param appName the name of the application to export.
+ * @return error code . If there is error, it add error code in context {@link #CONTEXT_LASTERRORCODE} field and
+ * exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ *
+ * Error codes can be :
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : action finished with no error.
+ *
{@link ApplicationManagerException#ERROR_AM_DOESNOTEXIST} : provided application does not exist.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ * @throws IOException all error that does not caused by user of this method.
+ */
+ public int exportApplicationXAR(String appName) throws XWikiException, IOException
+ {
+ return exportApplicationXAR(appName, true, false);
+ }
+
+ /**
+ * Export an application into XAR using Packaging plugin.
+ *
+ * @param appName the name of the application.
+ * @param recurse if true include all dependencies applications into XAR.
+ * @param withDocHistory if true export with documents history.
+ * @return error code . If there is error, it add error code in context {@link #CONTEXT_LASTERRORCODE} field and
+ * exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ *
+ * Error codes can be :
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : action finished with no error.
+ *
{@link ApplicationManagerException#ERROR_AM_DOESNOTEXIST} : provided application does not exist.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ * @throws IOException all error that does not caused by user of this method.
+ */
+ public int exportApplicationXAR(String appName, boolean recurse, boolean withDocHistory) throws XWikiException,
+ IOException
+ {
+ int returncode = XWikiExceptionApi.ERROR_NOERROR;
+
+ try {
+ this.applicationPackager.exportApplicationXAR(appName, recurse, withDocHistory, context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_EXPORTAPP, appName), e);
+
+ returncode = e.getCode();
+ }
+
+ return returncode;
+ }
+
+ /**
+ * Import attached application XAR into current wiki and do all actions needed to installation an application. See
+ * {@link #reloadApplication(String)} for more.
+ *
+ * @param packageName the name of the attached XAR file to import.
+ * @return error code . If there is error, it add error code in context {@link #CONTEXT_LASTERRORCODE} field and
+ * exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ *
+ * Error codes can be :
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : action finished with no error.
+ *
{@link XWikiException#ERROR_XWIKI_ACCESS_DENIED} : context's user don't have rights to do this
+ * action.
+ *
{@link ApplicationManagerException#ERROR_AM_DOESNOTEXIST} : provided application does not exist.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public int importApplication(String packageName) throws XWikiException
+ {
+ if (!hasAdminRights()) {
+ return XWikiException.ERROR_XWIKI_ACCESS_DENIED;
+ }
+
+ int returncode = XWikiExceptionApi.ERROR_NOERROR;
+
+ try {
+ this.applicationPackager.importApplication(context.getDoc(), packageName, this.messageTool.get(
+ ApplicationManagerMessageTool.COMMENT_IMPORTAPPLICATION, packageName), context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_IMPORTAPP, packageName), e);
+
+ returncode = e.getCode();
+ }
+
+ return returncode;
+ }
+
+ /**
+ * Reload xwiki application. It means :
+ *
+ *
update XWikiPreferences with application translation documents.
+ *
+ *
+ * @param appName the name of the application to reload.
+ * @return error code . If there is error, it add error code in context {@link #CONTEXT_LASTERRORCODE} field and
+ * exception in context's {@link #CONTEXT_LASTEXCEPTION} field.
+ *
+ * Error codes can be :
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : action finished with no error.
+ *
{@link XWikiException#ERROR_XWIKI_ACCESS_DENIED} : context's user don't have rights to do this
+ * action.
+ *
{@link ApplicationManagerException#ERROR_AM_DOESNOTEXIST} : provided application does not exist.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public int reloadApplication(String appName) throws XWikiException
+ {
+ if (!hasAdminRights()) {
+ return XWikiException.ERROR_XWIKI_ACCESS_DENIED;
+ }
+
+ int returncode = XWikiExceptionApi.ERROR_NOERROR;
+
+ try {
+ XWikiApplication app = this.applicationManager.getApplication(appName, context, true);
+ this.applicationManager.reloadApplication(app, this.messageTool.get(
+ ApplicationManagerMessageTool.COMMENT_RELOADAPPLICATION, app.getAppName()), context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_RELOADAPP, appName), e);
+
+ returncode = e.getCode();
+ }
+
+ return returncode;
+ }
+
+ /**
+ * Reload all xwiki applications. It means : - update XWikiPreferences with each application translation documents
+ *
+ * @return error code.
+ *
+ *
{@link XWikiExceptionApi#ERROR_NOERROR} : action finished with no error.
+ *
+ * {@link XWikiException#ERROR_XWIKI_ACCESS_DENIED} : context's user don't have rights to do this
+ * action.
+ *
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public int reloadAllApplications() throws XWikiException
+ {
+ if (!hasAdminRights()) {
+ return XWikiException.ERROR_XWIKI_ACCESS_DENIED;
+ }
+
+ int returncode = XWikiExceptionApi.ERROR_NOERROR;
+
+ try {
+ this.applicationManager.reloadAllApplications(
+ this.messageTool.get(ApplicationManagerMessageTool.COMMENT_RELOADALLAPPLICATIONS), context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_REALOADALLAPPS), e);
+
+ returncode = e.getCode();
+ }
+
+ return returncode;
+ }
+
+ /**
+ * Get the current wiki root application.
+ *
+ * @return the root application descriptor document. If can't find root application return null.
+ * @throws XWikiException all error that does not caused by user of this method.
+ */
+ public XWikiApplication getRootApplication() throws XWikiException
+ {
+ XWikiApplication app = null;
+
+ try {
+ app = this.applicationManager.getRootApplication(context);
+ } catch (ApplicationManagerException e) {
+ logError(this.messageTool.get(ApplicationManagerMessageTool.LOG_GETROOTAPP), e);
+ }
+
+ return app;
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationPackager.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationPackager.java
new file mode 100644
index 000000000..c85d904ce
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/ApplicationPackager.java
@@ -0,0 +1,148 @@
+package com.xpn.xwiki.plugin.applicationmanager;
+
+import java.io.IOException;
+import java.util.Set;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiAttachment;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.plugin.applicationmanager.core.plugin.XWikiPluginMessageTool;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplication;
+import com.xpn.xwiki.plugin.applicationmanager.doc.XWikiApplicationClass;
+import com.xpn.xwiki.plugin.packaging.DocumentInfo;
+import com.xpn.xwiki.plugin.packaging.DocumentInfoAPI;
+import com.xpn.xwiki.plugin.packaging.PackageAPI;
+
+/**
+ * Provide method to install export applications.
+ *
+ * @version $Id$
+ * @since 1.9
+ */
+public class ApplicationPackager
+{
+ /**
+ * The name of the internal packaging plugin.
+ */
+ private static final String PACKAGEPLUGIN_NAME = "package";
+
+ /**
+ * The message tool to use to generate error or comments.
+ */
+ private XWikiPluginMessageTool messageTool;
+
+ /**
+ * Protected API for managing applications.
+ */
+ private ApplicationManager applicationManager;
+
+ // ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @param messageTool the message tool
+ */
+ public ApplicationPackager(XWikiPluginMessageTool messageTool)
+ {
+ this.messageTool = messageTool;
+
+ this.applicationManager = new ApplicationManager(this.messageTool);
+ }
+
+ /**
+ * Get the {@link XWikiPluginMessageTool} to use with ApplicationManager.
+ *
+ * @param context the XWiki context.
+ * @return a translated strings manager.
+ */
+ public XWikiPluginMessageTool getMessageTool(XWikiContext context)
+ {
+ return this.messageTool != null ? this.messageTool : ApplicationManagerMessageTool.getDefault(context);
+ }
+
+ /**
+ * Export an application into XAR using Packaging plugin.
+ *
+ * @param appName the name of the application to export.
+ * @param recurse indicate if dependencies applications has to be included in the package.
+ * @param withDocHistory indicate if history of documents is exported.
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting application descriptor document to export.
+ *
or getting application's documents to export.
+ *
or when apply export.
+ *
+ * @throws IOException error when apply export.
+ */
+ public void exportApplicationXAR(String appName, boolean recurse, boolean withDocHistory, XWikiContext context)
+ throws XWikiException, IOException
+ {
+ XWikiApplication app = this.applicationManager.getApplication(appName, context, true);
+
+ PackageAPI export = ((PackageAPI) context.getWiki().getPluginApi(PACKAGEPLUGIN_NAME, context));
+
+ export.setName(app.getAppName() + "-" + app.getAppVersion());
+
+ Set documents = app.getDocumentsNames(recurse, true);
+ for (String documentName : documents) {
+ export.add(documentName, DocumentInfo.ACTION_OVERWRITE);
+ }
+
+ export.setWithVersions(withDocHistory);
+
+ export.export();
+ }
+
+ /**
+ * Import attached application XAR into current wiki and do all actions needed to installation an application. See
+ * {@link #reloadApplication(XWikiApplication, String, XWikiContext)} for more.
+ *
+ * @param packageDoc the document where package to import is attached.
+ * @param packageName the name of the attached XAR file to import.
+ * @param comment a comment used update XWiki.XWikiPreferences.
+ * @param context the XWiki context.
+ * @throws XWikiException error when :
+ *
+ *
getting attached package file.
+ *
or load package in memory.
+ *
or installing loaded document in database
+ *
or apply application initialization for each application descriptor document.
+ *
+ */
+ public void importApplication(XWikiDocument packageDoc, String packageName, String comment, XWikiContext context)
+ throws XWikiException
+ {
+ XWikiAttachment packFile = packageDoc.getAttachment(packageName);
+
+ if (packFile == null) {
+ throw new ApplicationManagerException(XWikiException.ERROR_XWIKI_UNKNOWN, getMessageTool(context).get(
+ ApplicationManagerMessageTool.ERROR_IMORT_PKGDOESNOTEXISTS, packageName));
+ }
+
+ // Import
+ PackageAPI importer = ((PackageAPI) context.getWiki().getPluginApi(PACKAGEPLUGIN_NAME, context));
+
+ try {
+ importer.Import(packFile.getContent(context));
+ } catch (IOException e) {
+ throw new ApplicationManagerException(XWikiException.ERROR_XWIKI_UNKNOWN, getMessageTool(context).get(
+ ApplicationManagerMessageTool.ERROR_IMORT_IMPORT, packageName), e);
+ }
+
+ if (importer.install() == DocumentInfo.INSTALL_IMPOSSIBLE) {
+ throw new ApplicationManagerException(XWikiException.ERROR_XWIKI_UNKNOWN, getMessageTool(context).get(
+ ApplicationManagerMessageTool.ERROR_IMORT_INSTALL, packageName));
+ }
+
+ // Apply applications installation
+ for (DocumentInfoAPI docinfo : importer.getFiles()) {
+ XWikiDocument doc = docinfo.getDocInfo().getDoc();
+
+ if (XWikiApplicationClass.getInstance(context).isInstance(doc)) {
+ this.applicationManager.reloadApplication(
+ XWikiApplicationClass.getInstance(context).newXObjectDocument(doc, 0, context), comment, context);
+ }
+ }
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/core/api/XWikiExceptionApi.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/core/api/XWikiExceptionApi.java
new file mode 100644
index 000000000..83fd4b368
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/core/api/XWikiExceptionApi.java
@@ -0,0 +1,112 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager.core.api;
+
+import java.lang.reflect.Field;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Api;
+
+/**
+ * Permit to manipulate XWikiException in velocity code.
+ *
+ * @version $Id$
+ */
+public class XWikiExceptionApi extends Api
+{
+ /**
+ * No error.
+ */
+ public static final int ERROR_NOERROR = -1;
+
+ /**
+ * Error code that is used when requested error code does not exists.
+ */
+ public static final int ERROR_XWIKI_ERROR_DOES_NOT_EXIST = -2;
+
+ // ///////////////////////////////////////////////////////////:
+
+ /**
+ * Managed exception.
+ */
+ private XWikiException exception;
+
+ /**
+ * XWikiExceptionApi constructor.
+ *
+ * @param exception the XWiki exception to manage.
+ * @param context the XWiki context.
+ */
+ public XWikiExceptionApi(XWikiException exception, XWikiContext context)
+ {
+ super(context);
+ this.exception = exception;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ return this.exception.getMessage();
+ }
+
+ // ///////////////////////////////////////////////////////////:
+
+ /**
+ * Get static field error code value. This name targeting velocity to be able to use like
+ * "exception.SOME_ERROR_CODE".
+ *
+ * @param error the static field name.
+ * @return the static field value.
+ * @throws XWikiException ERROR_XWIKI_ERROR_DOES_NOT_EXIST No corresponding error code exist.
+ */
+ public int get(String error) throws XWikiException
+ {
+ try {
+ Field field = getClass().getField(error);
+
+ if (field.getType() == int.class) {
+ return ((Integer) field.get(null)).intValue();
+ }
+ } catch (Exception e) {
+ // Error code is not a XWikiExceptionApi field.
+ }
+
+ try {
+ Field field = this.exception.getClass().getField(error);
+
+ if (field.getType() == int.class) {
+ return ((Integer) field.get(null)).intValue();
+ }
+ } catch (Exception e) {
+ // Error when trying to retrieve error code value.
+ }
+
+ throw new XWikiException(XWikiException.MODULE_XWIKI,
+ ERROR_XWIKI_ERROR_DOES_NOT_EXIST,
+ "Error \"" + error + "\" code does not exist");
+ }
+}
diff --git a/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/core/doc/objects/classes/AbstractXClassManager.java b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/core/doc/objects/classes/AbstractXClassManager.java
new file mode 100644
index 000000000..f7b8bd1c4
--- /dev/null
+++ b/celements-xapp/component/src/main/java/com/xpn/xwiki/plugin/applicationmanager/core/doc/objects/classes/AbstractXClassManager.java
@@ -0,0 +1,1075 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package com.xpn.xwiki.plugin.applicationmanager.core.doc.objects.classes;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.xwiki.model.reference.ClassReference;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.rendering.syntax.Syntax;
+
+import com.celements.model.access.IModelAccessFacade;
+import com.celements.model.access.exception.DocumentSaveException;
+import com.celements.model.context.ModelContext;
+import com.celements.model.reference.RefBuilder;
+import com.celements.model.util.ModelUtils;
+import com.celements.model.util.ReferenceSerializationMode;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Document;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.classes.BaseClass;
+import com.xpn.xwiki.objects.classes.BooleanClass;
+
+/**
+ * Abstract implementation of XClassManager.
+ *
+ * This class has to be extended with at least :
+ *
+ *
overload {@link #updateBaseClass(BaseClass)}
+ *
in constructor call AbstractXClassManager constructor with a name that will be used to
+ * generate all the documents
+ * and spaces needed.
+ *
+ *
+ * @param
+ * the item class extending {@link XObjectDocument}.
+ * @version $Id$
+ * @see XClassManager
+ * @since Application Manager 1.0RC1
+ */
+public abstract class AbstractXClassManager implements XClassManager {
+
+ /**
+ * FullName of the default parent page for a document containing xwiki class.
+ */
+ private static final String DEFAULT_XWIKICLASS_PARENT = "XWiki.XWikiClasses";
+
+ /**
+ * The resource file extension containing pages contents.
+ */
+ private static final String DOCUMENTCONTENT_EXT = ".vm";
+
+ /**
+ * Resource path prefix for the class sheets documents content.
+ */
+ private static final String DOCUMENTCONTENT_SHEET_PREFIX = "sheets/";
+
+ /**
+ * Resource path prefix for the class templates documents content.
+ */
+ private static final String DOCUMENTCONTENT_TEMPLATE_PREFIX = "templates/";
+
+ /**
+ * Symbol used in HQL request to insert and protect value when executing the request.
+ */
+ private static final String HQL_PARAMETER_STRING = "?";
+
+ /**
+ * Space prefix of class document.
+ *
+ * @see #getClassSpace()
+ */
+ private final String classSpacePrefix;
+
+ /**
+ * Prefix of class document.
+ *
+ * @see #getClassPrefix()
+ */
+ private final String classPrefix;
+
+ /**
+ * Space of class document.
+ *
+ * @see #getClassSpace()
+ */
+ private final String classSpace;
+
+ /**
+ * Name of class document.
+ *
+ * @see #getClassName()
+ */
+ private final String className;
+
+ /**
+ * Full name of class document.
+ *
+ * @see #getClassFullName()
+ */
+ private final String classFullName;
+
+ private final ClassReference classReference;
+
+ /**
+ * Space of class sheet document.
+ *
+ * @see #getClassSpace()
+ */
+ private final String classSheetSpace;
+
+ /**
+ * Name of class sheet document.
+ *
+ * @see #getClassSheetName()
+ */
+ private final String classSheetName;
+
+ /**
+ * Full name of class sheet document.
+ *
+ * @see #getClassSheetFullName()
+ */
+ private final String classSheetFullName;
+ private DocumentReference classSheetDocRef;
+
+ /**
+ * Space of class template document.
+ *
+ * @see #getClassSpace()
+ */
+ private final String classTemplateSpace;
+
+ /**
+ * Name of class template document.
+ *
+ * @see #getClassTemplateName()
+ */
+ private final String classTemplateName;
+
+ /**
+ * Full name of class template document.
+ *
+ * @see #getClassTemplateDocRef()
+ */
+ private final DocumentReference classTemplateDocRef;
+
+ /**
+ * Default content of class template document.
+ */
+ private final String classSheetDefaultContent;
+
+ /**
+ * Default content of class sheet document.
+ */
+ private final String classTemplateDefaultContent;
+
+ /**
+ * Base class managed.
+ */
+ private BaseClass baseClass;
+
+ /**
+ * Indicate class Manager is updating class document.
+ */
+ private boolean checkingClass;
+
+ /**
+ * Indicate class Manager is updating class sheet document.
+ */
+ private boolean checkingClassSheet;
+
+ /**
+ * Indicate class Manager is updating class template document.
+ */
+ private boolean checkingClassTemplate;
+ private final IModelAccessFacade modelAccess;
+ private final ModelContext mContext;
+ private final ModelUtils modelUtils;
+
+ /**
+ * Constructor for AbstractXClassManager.
+ *
+ * @param prefix
+ * the prefix of class document.
+ * @see #AbstractXClassManager(String, String)
+ * @see #AbstractXClassManager(String, String, boolean)
+ */
+ protected AbstractXClassManager(String prefix, IModelAccessFacade modelAccess,
+ ModelContext mContext, ModelUtils modelUtils) {
+ this(XWIKI_CLASS_SPACE_PREFIX, prefix, modelAccess, mContext, modelUtils);
+ }
+
+ /**
+ * Constructor for AbstractXClassManager.
+ *
+ * @param spaceprefix
+ * the space prefix of class document.
+ * @param prefix
+ * the prefix of class document.
+ * @see #AbstractXClassManager(String)
+ * @see #AbstractXClassManager(String, String, boolean)
+ */
+ protected AbstractXClassManager(String spaceprefix, String prefix,
+ IModelAccessFacade modelAccess, ModelContext mContext, ModelUtils modelUtils) {
+ this(spaceprefix, prefix, true, modelAccess, mContext, modelUtils);
+ }
+
+ /**
+ * Constructor for AbstractXClassManager.
+ *
+ * @param spaceprefix
+ * the space of class document.
+ * @param prefix
+ * the prefix of class document.
+ * @param dispatch
+ * Indicate if it had to use standard XWiki applications space names.
+ * @see #AbstractXClassManager(String)
+ * @see #AbstractXClassManager(String, String)
+ */
+ protected AbstractXClassManager(String spaceprefix, String prefix, boolean dispatch,
+ IModelAccessFacade modelAccess, ModelContext mContext, ModelUtils modelUtils) {
+ this.modelAccess = modelAccess;
+ this.mContext = mContext;
+ this.modelUtils = modelUtils;
+ this.classSpacePrefix = spaceprefix;
+ this.classPrefix = prefix;
+
+ this.classSpace = dispatch ? classSpacePrefix + XWIKI_CLASS_SPACE_SUFFIX : classSpacePrefix;
+ this.className = classPrefix + XWIKI_CLASS_SUFFIX;
+ this.classFullName = classSpace + XObjectDocument.SPACE_DOC_SEPARATOR + className;
+ this.classReference = RefBuilder.create().space(classSpace).doc(className)
+ .build(ClassReference.class);
+
+ this.classSheetSpace = dispatch ? classSpacePrefix + XWIKI_CLASSSHEET_SPACE_SUFFIX
+ : classSpacePrefix;
+ this.classSheetName = classPrefix + XWIKI_CLASSSHEET_SUFFIX;
+ this.classSheetFullName = classSheetSpace + XObjectDocument.SPACE_DOC_SEPARATOR
+ + classSheetName;
+ this.classSheetDocRef = RefBuilder.from(mContext.getWikiRef()).space(classSheetSpace)
+ .doc(classSheetName).build(DocumentReference.class);
+
+ this.classTemplateSpace = dispatch ? classSpacePrefix + XWIKI_CLASSTEMPLATE_SPACE_SUFFIX
+ : classSpacePrefix;
+ this.classTemplateName = classPrefix + XWIKI_CLASSTEMPLATE_SUFFIX;
+ this.classTemplateDocRef = RefBuilder.from(mContext.getWikiRef()).space(classTemplateSpace)
+ .doc(classTemplateName).build(DocumentReference.class);
+
+ this.classSheetDefaultContent = "## you can modify this page to customize the presentation of your object\n\n"
+ + "1 Document $doc.name\n\n#set($class = $doc.getObject(\"" + classFullName
+ + "\").xWikiClass)\n"
+ + "\n" + "
\n" + " #foreach($prop in $class.properties)\n"
+ + "
${prop.prettyName}
\n"
+ + "
$doc.display($prop.getName())
\n #end\n" + "
\n";
+
+ this.classTemplateDefaultContent = "#includeForm(\"" + classSheetFullName + "\")\n";
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.applicationmanager.core.doc.objects.classes.XClassManager#getClassSpacePrefix()
+ */
+ @Override
+ public String getClassSpacePrefix() {
+ return this.classSpacePrefix;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassSpace()
+ */
+ @Override
+ public String getClassSpace() {
+ return this.classSpace;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassPrefix()
+ */
+ @Override
+ public String getClassPrefix() {
+ return this.classPrefix;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassName()
+ */
+ @Override
+ public String getClassName() {
+ return this.className;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassFullName()
+ */
+ @Deprecated
+ @Override
+ public String getClassFullName() {
+ return this.classFullName;
+ }
+
+ @Override
+ public DocumentReference getClassDocRef() {
+ return this.classReference.getDocRef();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassTemplateName()
+ */
+ @Override
+ public String getClassTemplateSpace() {
+ return this.classTemplateSpace;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassTemplateName()
+ */
+ @Override
+ public String getClassTemplateName() {
+ return this.classTemplateName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassTemplateFullName()
+ */
+ @Deprecated
+ @Override
+ public String getClassTemplateFullName() {
+ return modelUtils.serializeRef(this.classTemplateDocRef, ReferenceSerializationMode.LOCAL);
+ }
+
+ @Override
+ public DocumentReference getClassTemplateDocRef() {
+ return this.classTemplateDocRef;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassSheetName()
+ */
+ @Override
+ public String getClassSheetSpace() {
+ return this.classSheetSpace;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassSheetName()
+ */
+ @Override
+ public String getClassSheetName() {
+ return this.classSheetName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassSheetFullName()
+ */
+ @Deprecated
+ @Override
+ public String getClassSheetFullName() {
+ return this.classSheetFullName;
+ }
+
+ @Override
+ public DocumentReference getClassSheetDocRef() {
+ return this.classSheetDocRef;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.applicationmanager.core.doc.objects.classes.XClassManager#forceValidDocumentName()
+ */
+ @Override
+ public boolean forceValidDocumentName() {
+ return false;
+ }
+
+ /**
+ * Check if all necessary documents for manage this class in this context exists and update.
+ * Create if not exists.
+ * Thread safe.
+ *
+ * @param context
+ * the XWiki context.
+ * @throws XWikiException
+ * error when saving documents.
+ * @see #checkClassDocument(XWikiContext)
+ */
+ protected void check(XWikiContext context) throws XWikiException {
+ checkClassDocument(context);
+ checkClassSheetDocument(context);
+ checkClassTemplateDocument(context);
+ }
+
+ /**
+ * Check if class document exists in this context and update. Create if not exists.
+ *
+ * @param context
+ * the XWiki context.
+ * @throws XWikiException
+ * error when saving document.
+ */
+ private void checkClassDocument(XWikiContext context) throws XWikiException {
+ if (this.checkingClass) {
+ return;
+ }
+
+ this.checkingClass = true;
+
+ try {
+ XWikiDocument doc = modelAccess.getOrCreateDocument(getClassDocRef());
+ doc.setParent(DEFAULT_XWIKICLASS_PARENT);
+ boolean needsUpdate = doc.isNew();
+
+ this.baseClass = doc.getxWikiClass();
+
+ needsUpdate |= updateBaseClass(this.baseClass);
+
+ if (doc.isNew() || needsUpdate) {
+ modelAccess.saveDocument(doc);
+ }
+ } catch (DocumentSaveException exp) {
+ throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS,
+ XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_DOC, exp.getMessage(), exp);
+ } finally {
+ this.checkingClass = false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassSheetDefaultContent()
+ */
+ @Override
+ public String getClassSheetDefaultContent() {
+ return this.classSheetDefaultContent;
+ }
+
+ /**
+ * Load an entire resource text file into {@link String}.
+ *
+ * @param path
+ * the path to the resource file.
+ * @return the entire content of the resource text file.
+ */
+ private String getResourceDocumentContent(String path) {
+ InputStream in = this.getClass().getClassLoader().getResourceAsStream(path);
+
+ if (in != null) {
+ try {
+ StringBuffer content = new StringBuffer(in.available());
+
+ InputStreamReader isr = new InputStreamReader(in);
+ try (isr) {
+ BufferedReader reader = new BufferedReader(isr);
+ for (String str = reader.readLine(); str != null; str = reader.readLine()) {
+ content.append(str);
+ content.append('\n');
+ }
+ }
+
+ return content.toString();
+ } catch (IOException e) {
+ // No resource file as been found or there is a problem when read it.
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Check if class sheet document exists in this context and update. Create if not exists.
+ *
+ * @param context
+ * the XWiki context.
+ * @throws XWikiException
+ * error when saving document.
+ */
+ private void checkClassSheetDocument(XWikiContext context) throws XWikiException {
+ if (this.checkingClassSheet) {
+ return;
+ }
+
+ this.checkingClassSheet = true;
+
+ try {
+ XWikiDocument doc = modelAccess.getOrCreateDocument(getClassSheetDocRef());
+ doc.setParent(getClassFullName());
+ boolean needsUpdate = doc.isNew();
+
+ if (doc.isNew()) {
+ String documentContentPath = DOCUMENTCONTENT_SHEET_PREFIX + getClassSheetFullName()
+ + DOCUMENTCONTENT_EXT;
+ String content = getResourceDocumentContent(documentContentPath);
+ doc.setContent(content != null ? content : getClassSheetDefaultContent());
+ doc.setSyntax(Syntax.XWIKI_1_0);
+ }
+
+ if (doc.isNew() || needsUpdate) {
+ modelAccess.saveDocument(doc);
+ }
+ } catch (DocumentSaveException exp) {
+ throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS,
+ XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_DOC, exp.getMessage(), exp);
+ } finally {
+ this.checkingClassSheet = false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassTemplateDefaultContent()
+ */
+ @Override
+ public String getClassTemplateDefaultContent() {
+ return this.classTemplateDefaultContent;
+ }
+
+ /**
+ * Check if class template document exists in this context and update. Create if not exists.
+ *
+ * @param context
+ * the XWiki context.
+ * @throws XWikiException
+ * error when saving document.
+ */
+ private void checkClassTemplateDocument(XWikiContext context) throws XWikiException {
+ if (this.checkingClassTemplate) {
+ return;
+ }
+
+ this.checkingClassTemplate = true;
+
+ try {
+ XWikiDocument doc = modelAccess.getOrCreateDocument(getClassTemplateDocRef());
+ boolean needsUpdate = doc.isNew();
+
+ if (doc.getObject(getClassFullName()) == null) {
+ doc.createNewObject(getClassFullName(), context);
+
+ needsUpdate = true;
+ }
+
+ if (doc.isNew()) {
+ String content = getResourceDocumentContent(
+ DOCUMENTCONTENT_TEMPLATE_PREFIX + getClassTemplateFullName()
+ + DOCUMENTCONTENT_EXT);
+ doc.setContent(content != null ? content : getClassTemplateDefaultContent());
+ doc.setSyntax(Syntax.XWIKI_1_0);
+
+ doc.setParent(getClassFullName());
+ }
+
+ needsUpdate |= updateClassTemplateDocument(doc);
+
+ if (doc.isNew() || needsUpdate) {
+ modelAccess.saveDocument(doc);
+ }
+ } catch (DocumentSaveException exp) {
+ throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS,
+ XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_DOC, exp.getMessage(), exp);
+ } finally {
+ this.checkingClassTemplate = false;
+ }
+ }
+
+ /**
+ * @param value
+ * the {@link Boolean} value to convert.
+ * @return the converted int value.
+ */
+ protected int intFromBoolean(Boolean value) {
+ return value == null ? -1 : (value ? 1 : 0);
+ }
+
+ /**
+ * Initialize template document with default content.
+ *
+ * @param doc
+ * the class template document that will be saved.
+ * @return true if doc modified.
+ */
+ protected boolean updateClassTemplateDocument(XWikiDocument doc) {
+ return false;
+ }
+
+ /**
+ * Set the value of a boolean field in a document.
+ *
+ * @param doc
+ * the document to modify.
+ * @param fieldName
+ * the name of the field.
+ * @param value
+ * the value.
+ * @return true if doc modified.
+ */
+ protected boolean updateDocStringValue(XWikiDocument doc, String fieldName, String value) {
+ boolean needsUpdate = false;
+
+ if (!value.equals(doc.getStringValue(getClassFullName(), fieldName))) {
+ doc.setStringValue(getClassFullName(), fieldName, value);
+ needsUpdate = true;
+ }
+
+ return needsUpdate;
+ }
+
+ /**
+ * Set the value of a boolean field in a document.
+ *
+ * @param doc
+ * the document to modify.
+ * @param fieldName
+ * the name of the field.
+ * @param value
+ * the value.
+ * @return true if doc modified.
+ */
+ protected boolean updateDocBooleanValue(XWikiDocument doc, String fieldName, Boolean value) {
+ boolean needsUpdate = false;
+
+ int intvalue = intFromBoolean(value);
+
+ if (intvalue != doc.getIntValue(getClassFullName(), fieldName)) {
+ doc.setIntValue(getClassFullName(), fieldName, intvalue);
+ needsUpdate = true;
+ }
+
+ return needsUpdate;
+ }
+
+ /**
+ * Configure BaseClass.
+ *
+ * @param baseClass
+ * the baseClass to configure.
+ * @return true if baseClass modified.
+ */
+ protected boolean updateBaseClass(BaseClass baseClass) {
+ boolean needUpdate = false;
+
+ if (!baseClass.getName().equals(getClassFullName())) {
+ baseClass.setName(getClassFullName());
+ needUpdate = true;
+ }
+
+ return needUpdate;
+ }
+
+ /**
+ * Set the default value of a boolean field of a XWiki class.
+ *
+ * @param baseClass
+ * the XWiki class.
+ * @param fieldName
+ * the name of the field.
+ * @param value
+ * the default value.
+ * @return true if baseClass modified.
+ */
+ protected boolean updateBooleanClassDefaultValue(BaseClass baseClass, String fieldName,
+ Boolean value) {
+ boolean needsUpdate = false;
+
+ BooleanClass bc = (BooleanClass) baseClass.get(fieldName);
+
+ int old = bc.getDefaultValue();
+ int intvalue = intFromBoolean(value);
+
+ if (intvalue != old) {
+ bc.setDefaultValue(intvalue);
+ needsUpdate = true;
+ }
+
+ return needsUpdate;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getBaseClass()
+ */
+ @Override
+ public BaseClass getBaseClass() {
+ if (this.baseClass == null) {
+ this.baseClass = new BaseClass();
+ updateBaseClass(this.baseClass);
+ }
+
+ return this.baseClass;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassDocument(com.xpn.xwiki.XWikiContext)
+ */
+ @Override
+ public Document getClassDocument(XWikiContext context) throws XWikiException {
+ check(context);
+
+ return modelAccess.getOrCreateDocument(getClassDocRef()).newDocument(context);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassSheetDocument(com.xpn.xwiki.XWikiContext)
+ */
+ @Override
+ public Document getClassSheetDocument(XWikiContext context) throws XWikiException {
+ check(context);
+
+ return modelAccess.getOrCreateDocument(getClassSheetDocRef()).newDocument(context);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getClassTemplateDocument(com.xpn.xwiki.XWikiContext)
+ */
+ @Override
+ public Document getClassTemplateDocument(XWikiContext context) throws XWikiException {
+ check(context);
+
+ return modelAccess.getOrCreateDocument(getClassTemplateDocRef()).newDocument(context);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#isInstance(com.xpn.xwiki.doc.XWikiDocument)
+ */
+ @Override
+ public boolean isInstance(XWikiDocument doc) {
+ return (doc.getObjectNumbers(getClassFullName()) > 0)
+ && (!forceValidDocumentName() || isValidName(doc.getFullName()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#isInstance(com.xpn.xwiki.doc.XWikiDocument)
+ */
+ @Override
+ public boolean isInstance(Document doc) {
+ return (doc.getObjectNumbers(getClassFullName()) > 0)
+ && (!forceValidDocumentName() || isValidName(doc.getFullName()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.applicationmanager.core.doc.objects.classes.XClassManager#isValidName(java.lang.String)
+ */
+ @Override
+ public boolean isValidName(String fullName) {
+ return getItemDefaultName(fullName) != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getItemDocumentDefaultName(java.lang.String, XWikiContext)
+ */
+ @Override
+ public String getItemDocumentDefaultName(String itemName, XWikiContext context) {
+ String cleanedItemName = context != null
+ ? context.getWiki().clearName(itemName, true, true, context)
+ : itemName;
+
+ return getClassPrefix() + cleanedItemName.substring(0, 1).toUpperCase()
+ + cleanedItemName.substring(1).toLowerCase();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see XClassManager#getItemDocumentDefaultFullName(java.lang.String, XWikiContext)
+ */
+ @Override
+ public String getItemDocumentDefaultFullName(String itemName, XWikiContext context) {
+ return getClassSpacePrefix() + XObjectDocument.SPACE_DOC_SEPARATOR
+ + getItemDocumentDefaultName(itemName, context);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.applicationmanager.core.doc.objects.classes.XClassManager#getItemDefaultName(java.lang.String)
+ */
+ @Override
+ public String getItemDefaultName(String docFullName) {
+ String prefix = getClassSpacePrefix() + XObjectDocument.SPACE_DOC_SEPARATOR + getClassPrefix();
+
+ if (!docFullName.startsWith(prefix)) {
+ return null;
+ }
+
+ return docFullName.substring(prefix.length()).toLowerCase();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see com.xpn.xwiki.plugin.applicationmanager.core.doc.objects.classes.XClassManager#getXObjectDocument(java.lang.String,
+ * int, boolean, com.xpn.xwiki.XWikiContext)
+ */
+ @Override
+ public T getXObjectDocument(String itemName, int objectId, boolean validate, XWikiContext context)
+ throws XWikiException {
+ XWikiDocument doc = context.getWiki()
+ .getDocument(getItemDocumentDefaultFullName(itemName, context), context);
+
+ if (doc.isNew() || !isInstance(doc)) {
+ throw new XObjectDocumentDoesNotExistException(itemName + " object does not exist");
+ }
+
+ return newXObjectDocument(doc, objectId, context);
+ }
+
+ /**
+ * Construct HQL where clause to use with {@link com.xpn.xwiki.store.XWikiStoreInterface}
+ * "searchDocuments" methods.
+ *
+ * @param fieldDescriptors
+ * the list of fields name/value constraints. Format : [[fieldName1, typeField1,
+ * valueField1][fieldName2, typeField2, valueField2]].
+ * @param parameterValues
+ * the where clause values that replace the question marks (?).
+ * @return a HQL where clause.
+ */
+ @Override
+ public String createWhereClause(Object[][] fieldDescriptors, List