Create Module HowTo
Contents |
Introduction
this HowTo will take you through a process of creating your own module extension to OpenCms administration view.
Create Module
First you must create module container for your extension. Creation of a new module is described in detail at Defining_OpenCMS_structured_XML_content#Step_1_.E2.80.93_Create_the_module_and_configure_it. Make sure that you select all module folders to be created.
Create and Install Message Bundle
Next we need to create and install message bundle for our new module, so will be able to customise all workplace labels based on user's selected language. This requires the following steps:-
- Create messages class.
- Create action class and initiate the message bundle as part of the action class.
- Create your module specific workplace.properties file.
All these files must be published as part of your module /classes/ directory.
Create messages class
Message class is used to reference your module specific workplace.properties file at part of OpenCms. This is just a simple class extending org.opencms.i18n.A_CmsMessageBundle and the bundle name variable pointing to the module specific workplace.properties file location. Below is a sample Messages class in order to illustrate this.
package com.clicksandlinks.opencms.groupmail; import org.opencms.i18n.A_CmsMessageBundle; import org.opencms.i18n.I_CmsMessageBundle; public class Messages extends A_CmsMessageBundle { public static final String GUI_NEWSLETTER_MASSMAIL_TOOL_NAME_0 = "GUI_NEWSLETTER_MASSMAIL_TOOL_NAME_0"; public static final String ERR_PROBLEM_SENDING_EMAIL_0 = "ERR_PROBLEM_SENDING_EMAIL_0"; public static final String ERR_BAD_SENDER_ADDRESS_0 = "ERR_BAD_SENDER_ADDRESS_0"; public static final String ERR_BAD_SUBJECT_FIELD_0 = "ERR_BAD_SUBJECT_FIELD_0"; public static final String ERR_BAD_SUBJECT_MESSAGE_0 = "ERR_BAD_SUBJECT_MESSAGE_0"; public static final String ERR_BAD_TO_GROUP_0 = "ERR_BAD_TO_GROUP_0"; public static final String GUI_USER_EDITOR_LABEL_IDENTIFICATION_BLOCK_COMPOSE_EMAIL_0 = "GUI_USER_EDITOR_LABEL_IDENTIFICATION_BLOCK_COMPOSE_EMAIL_0"; public static final String MODULE_DATE_FORMAT = "MODULE_DATE_FORMAT"; public static final String MODULE_EMAIL_SUBJECT_PREFIX = "MODULE_EMAIL_SUBJECT_PREFIX"; private static final String BUNDLE_NAME = "com.clicksandlinks.opencms.groupmail.workplace"; private static final I_CmsMessageBundle INSTANCE = new Messages(); private Messages() { // hide the constructor } public static I_CmsMessageBundle get() { return INSTANCE; } public String getBundleName() { return BUNDLE_NAME; } }
Create action class and initiate the message bundle as part of the action class
Action class is used to register the message bundle with OpenCms at start up. Again this is just a simple class this time implementing org.opencms.module.I_CmsModuleAction. The important thing here is to call the message bundle as part of the action class initialize method, so the message bundle gets loaded. Sample class below should demonstrate how to achieve this.
package com.clicksandlinks.opencms.groupmail; import org.opencms.configuration.CmsConfigurationManager; import org.opencms.db.CmsPublishList; import org.opencms.file.CmsObject; import org.opencms.main.CmsEvent; import org.opencms.module.CmsModule; import org.opencms.module.I_CmsModuleAction; import org.opencms.report.I_CmsReport; public class ActionClass implements I_CmsModuleAction { public void initialize(CmsObject adminCms, CmsConfigurationManager configurationManager, CmsModule module) { //Do a dummy load from resource bundle, so it gets registred with OpenCms. try{ Messages.get().getBundle(adminCms.getRequestContext().getLocale()).getResourceBundle(); }catch(Exception e){ // Do not throw anything. } } public void moduleUninstall(CmsModule module) { // Do nothing. } public void moduleUpdate(CmsModule module) { // Do nothing. } public void publishProject(CmsObject cms, CmsPublishList publishList, int backupTagId, I_CmsReport report) { // Do nothing. } public void shutDown(CmsModule module) { // Do nothing. } public void cmsEvent(CmsEvent event) { // Do nothing. } }
After you have created and uploaded the action class in module /classes/ directory you also need to register the action class with your module. This can be done in the module management by editing the "Action class" value.
Create your module specific workplace.properties file
Next we need to create a text file called workplace.properties in your module /classes/ folder. This file includes all language captions for your module. Below you can see a sample file on how your workplace.properties might look like. Creating a worplace.properties file for another language is easy. You just need to create another file with the required language suffix. For example for Finnish locale the properties file would be called workplace_fi.properties.
# Generic module settings. MODULE_DATE_FORMAT=dd/MM/yy HH:mm:ss MODULE_EMAIL_SUBJECT_PREFIX=[Mail from OpenCms] #Administration view group name. GUI_NEWSLETTER_MANAGEMENT_TOOL_GROUP_0=Administration #Generic application captions. ERR_PROBLEM_SENDING_EMAIL_0=Email could not be sent. Please contact your system administrator in order to check that the email server is configured correctly for the system. # Tool root folder captions. GUI_NEWSLETTER_MANAGEMENT_TOOL_NAME_0=Newsletter Management GUI_NEWSLETTER_MANAGEMENT_TOOL_HELP_0=Click here to send mass emails and newsletters to selected groups of users. # Mass mail tool folder captions. GUI_NEWSLETTER_MASSMAIL_TOOL_NAME_0=Mass Mail GUI_NEWSLETTER_MASSMAIL_TOOL_HELP_0=Click here to send mass email message to selected user groups. # Mass mail tool captions. GUI_USER_EDITOR_LABEL_IDENTIFICATION_BLOCK_COMPOSE_EMAIL_0=Compose Email ERR_BAD_SENDER_ADDRESS_0=Sender email address in not valid. ERR_BAD_TO_GROUP_0=To group is either missing or is not valid content management group. ERR_BAD_SUBJECT_FIELD_0=Email subject cannot be empty. ERR_BAD_SUBJECT_MESSAGE_0=Email message body cannot be empty. label.massmail.currentDate=Date label.massmail.fromAddress=From label.massmail.fromAddress.help=The email address that will be used as sender in this email. label.massmail.toGroup=To Group label.massmail.toGroup.help=The user group that the email will be send to. label.massmail.emailAttachments=Attachment label.massmail.emailAttachments.help=Up to 5 optional attachment for the email from the download gallery. label.massmail.emailSubject=Subject label.massmail.emailSubject.help=The email message subject. label.massmail.emailMessage=Message label.massmail.emailMessage.help=The mail message body text. # Newsletter tool folder captions. GUI_NEWSLETTER_NEWSLETTER_TOOL_NAME_0=Newsletter GUI_NEWSLETTER_NEWSLETTER_TOOL_HELP_0=Click here to send a content newsletter to selected user groups.
Add administration point in OpenCms administration view
Options within OpenCms administration view are rendered based on folder structure at /system/workplace/admin/. New administration points and structures can be just simple added by adding folders in /system/workplace/admin/ and ensuring that all relevant properties are configured.
Create module administration structure
Your custom module structure can be create by adding folders under /system/workplace/admin/. New administration point must include a single folder at /system/workplace/admin/ (e.g. /system/workplace/admin/groupmail/) as root folder. Within the root folder you can have an unlimited number of sub options. For example, my group mail module has options mass mail (/system/workplace/admin/groupmail/massmail/) and newsletter (/system/workplace/admin/groupmail/newsletter/). If your folder is actual action folder, so instead of just displaying list of administration options you would like actually to do something, you need to place index.jsp in this folder and the index.jsp will be automatically used to render the action form for your administration option. Administration point action forms will be explained more in detail in the next chapter.
The following folder properties must be populated in order for the administation option to appear within OpenCms administration view. Please note that you need to restart the server before the administration view registers new administration points. Please note that it is possible to pull the language specific captions from your workplace.properties file by using macro ${key.YOUR_LABEL_NAME} e.g. ${key.GUI_NEWSLETTER_MANAGEMENT_TOOL_GROUP_0}.
Description This property is used to display some help text regarding your administration point at the top left corner of OpenCms administration view when the user hovers over the administration option.
NavImage This property includes a list of URIs to icon images for your administration option. Two images are required: one "big" and one "small" e.g. tools/groupmail/icons/big/groupmail.png:tools/groupmail/icons/small/groupmail.png.
NavText This property includes the key for the text that will be displayed for the administration option.
admintoolhandler-class This property defines the class that defines the access control to the administration option. OpenCms comes with range of default handlers e.g. org.opencms.workplace.tools.CmsDefaultToolHandler can be utilised if you wish to give access to all members of the Users group to your administration option. It is also possible to write your own handler, which case I would recomment you to use org.opencms.workplace.tools.CmsDefaultToolHandler as a template.
default-file Set this property to index.jsp, if you would like to use action form with this administration option.
Register admistration structure as module resources
Since the administration points are defined outside the module folder, we must add them as part of the module resources, so that they will get installed and uninstalled when we manage the module via OpenCms module management interface. two folders that you need to add are: the admin structure folder (in my case this is /system/workplace/admin/groupmail/) and the folder where the images are located (in my case this is /system/workplace/resources/tools/groupmail/). After this your module resources list should look something like below.
Administration point action forms
As discussed earlier the administration point action forms can be created by placing index.jsp file in the administration folder required and the jsp will be automatically loaded as default view for the administration option as long as folder property default-file is set to index.jsp. Administration action form includes two parts: the jsp and CmsAdmin class responsible for rendering the admin form.
JSP Part
The JSP part does not include any logic, but just simply calls the CmsAdmin class. Please see an example below.
<%@ page import="com.clicksandlinks.opencms.groupmail.workplace.tools.CmsAdminMassMail"%><% CmsAdminMassMail wp = new CmsAdminMassMail(pageContext, request, response); wp.displayDialog(); %>
CmsAdmin Class Part
CmsAdmin class is responsible for rendering and processing the administration form. CmsAdmin class must extend org.opencms.workplace.CmsWidgetDialog, which provides all required methods for form rendering and processing. I have attached below a sample class for my massmail application, however here are explained the main methods that must be overwritten in order to get your administration view working properly.
KEY_PREFIXThis is a unique identifier for your action form. It is used to process your form values, but also for the captions for the labels in your forms. The naming covension for form labels for workplace.properties file is label.KEY_PREFIX.field_name and the naming convension for the help bubbles is label.KEY_PREFIX.field_name.help (e.g. label.massmail.subject and label.massmail.subject.help).
public void actionCommit() This is the main method that processes the form after submission. While you are free to do anything you like here, I would recommend you to check for some error states first and then use the build in error mechanism to throw them. setCommitErrors methods helps you to do this and allows you to display error messages at the top of the action form.
If actionCommit method gets processed without any errors, OpenCms will automatically close the action form and return the user to the previous part of the administration view.
createDialogHtml(String dialog) This method is used for rendering the action form. It is quite static and you can pretty much copy and paste it from my sample code.
defineWidgets()
Instead of writing your own markup OpenCms allows you to use its build in widgets for this. In the package org.opencms.widgets you can see all available widgets, however the base idea is that you define a widget as shown in my sample code below and that then will be fully responsible for rendering and collecting data from the form field in question.
Each widget works as a bean meaning that in order for them to work you need to implement the required setter and getter methods. While it is possible to create setter and getter methods in your CmsAdmin class, the recommended way is to create a dedicated class container for the action form data.
If you decide to create a dedicated container class, you must also create an init method. In my sample code this is called initEmailMessage(). This method is always quite static, so you can pretty much copy it from my sample code.
protected void initMessages() This method initialises the resource bundle.
initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) This method is required to init some workplace settings. It is always pretty much the same, so you can just copy and paste from the sample code below.
package com.clicksandlinks.opencms.groupmail.workplace.tools; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.PageContext; import org.apache.commons.mail.EmailAttachment; import org.opencms.file.CmsFile; import org.opencms.file.CmsUser; import org.opencms.jsp.CmsJspActionElement; import org.opencms.mail.CmsMultiPartMail; import org.opencms.main.CmsIllegalArgumentException; import org.opencms.main.CmsSystemInfo; import org.opencms.main.OpenCms; import org.opencms.util.CmsStringUtil; import org.opencms.widgets.CmsDisplayWidget; import org.opencms.widgets.CmsDownloadGalleryWidget; import org.opencms.widgets.CmsGroupWidget; import org.opencms.widgets.CmsInputWidget; import org.opencms.widgets.CmsTextareaWidget; import org.opencms.workplace.CmsDialog; import org.opencms.workplace.CmsWidgetDialog; import org.opencms.workplace.CmsWidgetDialogParameter; import org.opencms.workplace.CmsWorkplaceSettings; import com.clicksandlinks.opencms.groupmail.Messages; public class CmsAdminMassMail extends CmsWidgetDialog { private String VALIDATION_EMAIL = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$"; public static final String KEY_PREFIX = "massmail"; public static final String[] PAGES = {"page1"}; protected EmailMessage _emailMessage; private CmsJspActionElement _jsp; public CmsAdminMassMail(CmsJspActionElement jsp) { super(jsp); _jsp=jsp; } public CmsAdminMassMail(PageContext context, HttpServletRequest req, HttpServletResponse res) { this(new CmsJspActionElement(context, req, res)); } public void actionCommit() { //Check that the sender email is valid. String fromAddress=_emailMessage.getFromAddress().trim(); if(fromAddress.length()==0 || !fromAddress.matches(VALIDATION_EMAIL)){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_SENDER_ADDRESS_0)))); return; } //Check the group. String toGroup=_emailMessage.getToGroup().trim(); if(toGroup.length()==0){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_TO_GROUP_0)))); return; }else{ try{ getCms().readGroup(toGroup); boolean inGroup=getCms().userInGroup(getCms().getRequestContext().currentUser().getName(),toGroup); if(!inGroup){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_TO_GROUP_0)))); return; } }catch(Exception e){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_TO_GROUP_0)))); return; } } //Check that the email subject and text are filled in. String emailSubject=_emailMessage.getEmailSubject().trim(); if(emailSubject.length()==0){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_SUBJECT_FIELD_0)))); return; } String emailMessage=_emailMessage.getEmailMessage().trim(); if(emailMessage.length()==0){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_SUBJECT_MESSAGE_0)))); return; } //Init email helper. CmsMultiPartMail emailHelper=new CmsMultiPartMail(); SimpleDateFormat formatEmail=new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'"); emailHelper.addHeader("Date",formatEmail.format(new Date())); emailHelper.setSubject(key(Messages.MODULE_EMAIL_SUBJECT_PREFIX)+emailSubject); try{ emailHelper.setFrom(fromAddress); }catch(Exception e){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_SENDER_ADDRESS_0)))); return; } try{ emailHelper.addTo(fromAddress); }catch(Exception e){ //Nothing to throw } try{ //Set all group members as bcc. List groupMembers=getCms().getUsersOfGroup(toGroup); for(int i=0;i<groupMembers.size();i++){ try{ emailHelper.addBcc(((CmsUser)groupMembers.get(i)).getEmail()); }catch(Exception ie){ //Nothing to throw } } }catch(Exception e){ //Nothing to throw } try{ emailHelper.setMsg(emailMessage); }catch(Exception e){ setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_SUBJECT_MESSAGE_0)))); return; } //Add attachments. List attachments=_emailMessage.getEmailAttachments(); String domain = _jsp.getRequest().getScheme()+"://"+_jsp.getRequest().getLocalAddr(); String port = ":"+_jsp.getRequest().getServerPort(); if(port.equals(":80")||port.equals(":443")) port=""; CmsSystemInfo systemInfo = OpenCms.getSystemInfo(); for(int i=0;i<attachments.size();i++){ try{ String attachmentLocationInVfs=attachments.get(i).toString(); CmsFile attachmentFile=getCms().readFile(attachmentLocationInVfs); String attachmentUrl=domain+port+systemInfo.getOpenCmsContext()+getCms().getSitePath(attachmentFile); System.out.println("OLLI DEBUG:"+attachmentUrl); EmailAttachment attachment=new EmailAttachment(); attachment.setName(attachmentFile.getName()); attachment.setDisposition(EmailAttachment.ATTACHMENT); attachment.setURL(new URL(attachmentUrl)); emailHelper.attach(attachment); }catch(Exception eii){ // Nothing to do. If cannot find the attachment from VFS then just ignore it and move on. } } //Send the mail. try{ emailHelper.send(); }catch(Exception e){ e.printStackTrace(); setCommitErrors(Collections.singletonList(new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PROBLEM_SENDING_EMAIL_0)))); return; } } protected String createDialogHtml(String dialog) { StringBuffer result = new StringBuffer(1024); result.append(createWidgetTableStart()); // show error header once if there were validation errors result.append(createWidgetErrorHeader()); if (dialog.equals(PAGES[0])) { // create the widgets for the first dialog page result.append(dialogBlockStart(key(Messages.GUI_USER_EDITOR_LABEL_IDENTIFICATION_BLOCK_COMPOSE_EMAIL_0))); result.append(createWidgetTableStart()); result.append(createDialogRowsHtml(0, 5)); result.append(createWidgetTableEnd()); result.append(dialogBlockEnd()); } result.append(createWidgetTableEnd()); return result.toString(); } protected void defineWidgets() { //Init the email message bean. initEmailMessage(); // set the key prefix. setKeyPrefix(KEY_PREFIX); // widgets to display addWidget(new CmsWidgetDialogParameter(this, "currentDate", PAGES[0], new CmsDisplayWidget())); addWidget(new CmsWidgetDialogParameter(_emailMessage, "fromAddress", PAGES[0], new CmsInputWidget())); addWidget(new CmsWidgetDialogParameter(_emailMessage, "toGroup", PAGES[0], new CmsGroupWidget(null,getCms().getRequestContext().currentUser().getName()))); addWidget(new CmsWidgetDialogParameter(_emailMessage, "emailAttachments", "", PAGES[0], new CmsDownloadGalleryWidget(), 0, 5)); addWidget(new CmsWidgetDialogParameter(_emailMessage, "emailSubject", PAGES[0], new CmsInputWidget())); addWidget(new CmsWidgetDialogParameter(_emailMessage, "emailMessage", "", PAGES[0], new CmsTextareaWidget(30), 1, 1)); } public void setCurrentDate(String newValue){ //This field is always generated from CMS, so nothing to do here. } public String getCurrentDate(){ SimpleDateFormat format=new SimpleDateFormat(key(Messages.MODULE_DATE_FORMAT)); return format.format(new Date()); } protected String[] getPageArray() { return PAGES; } protected void initMessages() { // add specific dialog resource bundle addMessages(Messages.get().getBundleName()); // add default resource bundles super.initMessages(); } protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) { // initialize parameters and dialog actions in super implementation super.initWorkplaceRequestValues(settings, request); // save the current state of the user and pwd (may be changed because of the widget values) Map dialogObject = new HashMap(); setDialogObject(dialogObject); } protected void initEmailMessage() { Object o = null; if (!CmsStringUtil.isEmpty(getParamAction()) && !CmsDialog.DIALOG_INITIAL.equals(getParamAction())) { //this is not the initial call, get email message from session o = getDialogObject(); } if (o == null || !(o instanceof EmailMessage)) { // create a new module _emailMessage = new EmailMessage(getCms()); } else { // reuse module stored in session _emailMessage = (EmailMessage)((EmailMessage)o).clone(); } } }