Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The explanation above only describes how to create a Monitoring Dashboard plugin but does not cover how to develop your own indicator set(s). That is done entirely in Java. See the wmaxmfindicators bundle for an example of how to create your own custom indicators. As a starting point you should implement an XMFIndicatorProvider that enables the registration of your custom indicators. See the file DefaultIndicatorsServiceImpl.java in wmaxmfindicators. Refer to the Javadoc for wmaxmfapi in the wm-addon-monitoring package for the interfaces offered by the framework.

...


Back to top


...

Custom Content and the Is Used in Widget
Anchor
custom_media_items_and_the_is_used_in_widget
custom_media_items_and_the_is_used_in_widget


Panel
borderColor#0081C0
titleColor#0081C0

The following applies to XperienCentral versions 10.22.0 and higher.


The Is Used In widget shows an overview of content items that are using the currently selected content item. Before In XperienCentral versions 10.22.0 and earlier, the usage of content items referring to a custom content type was not detected for content items from a custom content type, The widget would only show a count of 0 used content items. The API of several content items in XperienCentral has been extended with a method for implementing this used-in relationship for custom content types. This functionality is available via the new abstract method:

...

Code Block
themeEclipse
package com.gxwebmanager.helloworld.helloworldpagemetadata.pagemetadata;
import java.util.Collections;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import nl.gx.webmanager.cms.core.RelatedDownloadLink;
import nl.gx.webmanager.cms.core.RelatedInternalLink;
import nl.gx.webmanager.cms.core.RelatedLink;
import nl.gx.webmanager.cms.core.RelatedMediaItemLink;
import nl.gx.webmanager.cms.core.RelatedResourceLink;
import nl.gx.webmanager.cms.mediarepository.MediaItemDownloadVersion;
import nl.gx.webmanager.services.contentdomain.api.ContentDomainException;
import nl.gx.webmanager.services.contentdomain.api.ContentDomainResolver;
import nl.gx.webmanager.services.contentindex.adapter.FieldAdapter;
import nl.gx.webmanager.services.framework.spi.FrameworkException;
import nl.gx.webmanager.services.framework.spi.FrameworkFactory;
   /**
   * Adapter that takes a content item, and returns a String-reference to it, e.g. page-32423. Logs
   * a warning and returns null if the given item could not be resolved.
   *
   * @see ContentDomainResolver
   */
public class ContentReferenceFieldAdapter implements FieldAdapter<Object> {
private static final Logger LOG = Logger.getLogger(ContentReferenceFieldAdapter.class.getName());
@Override
   public boolean isLanguageSpecific() {
   return false;
   }
@Override
   public Object adapt(Object item, Locale forLocale) {
   if (item == null) {
   return null;
   }
if (item instanceof RelatedLink) {
   return adaptLinkReference(item, forLocale);
   }
return getItemId(item);
   }
private String getItemId(Object item) {
   try {
   ContentDomainResolver resolver = (ContentDomainResolver) FrameworkFactory.getInstance().getFramework().getService(ContentDomainResolver.class.getName());
   return resolver.entityToId(item);
   } catch (FrameworkException | ContentDomainException e) {
   LOG.log(Level.WARNING, "Failed to retrieve content reference for " + item, e);
   }
   return null;
   }
private Object adaptLinkReference(Object link, Locale forLocale) {
   if (link instanceof RelatedInternalLink) {
   return adapt(((RelatedInternalLink) link).getPage(), forLocale);
   } else if (link instanceof RelatedMediaItemLink) {
   return adapt(((RelatedMediaItemLink) link).getMediaItem(), forLocale);
   } else if (link instanceof RelatedDownloadLink) {
   MediaItemDownloadVersion downloadVersion = ((RelatedDownloadLink) link).getInContextDownloadMediaItemDownloadVersion();
   if (downloadVersion != null) {
   String itemId = getItemId(downloadVersion.getContentItem());
   if (itemId != null) {
   return Collections.singletonMap("download", itemId);
   }
   }
   } else if (link instanceof RelatedResourceLink) {
   return adapt(((RelatedResourceLink) link).getResourceInstance(), forLocale);
   }
return null;
   }
}

...


Back to top



...

Developing Custom Bulk Actions


Panel
borderColor#0081C0
titleColor#0081C0

The following applies to XperienCentral versions 10.19 R28 and higher.

In a standard installation, XperienCentral users are managed in the Authorization component. Along with the Authorization component, there are two types of credentials sets that are not targeted at the editorial tasks but are infrastructure-related. By default these credential sets are managed by the following properties in the XperienCentral Setup Tool (General tab):

These credentials sets are for:

  • internal_http_authentication_password / internal_http_authentication_username needed for internal_http_use_authentication or internal_http_use_form_authentication
  • http_proxy_username / http_proxy_password needed for http_use_proxy

Because these credentials sets can be managed in an organizational credential storage system external to XperienCentral, you can create an integration with this thereby removing the need to fill in these credential properties in the Setup Tool.

This can be accomplished via your own integration component by creating a plugin that implements the Credentials Service Provider. When there is no plugin active that implements the credentials services, a look-up is done on the settings internal_http_use_authentication or internal_http_use_form_authentication in the "application_settings" section in the General tab of the Setup Tool. If a plugin is active that implements the XperienCentral credentials service, the username/password combination from the plugin is used to authorize the user.

When a credentials provider plugin is created, the service in the activator needs to implement the CredentialsProviderService and CredentialsProvider classes. The following is an example implementation of a credential provider.

 

Beginning in XperienCentral R28, it is possible to define your own bulk actions for content items. You can, for example, add the possibility to publish multiple content items at once, change the publication or expiration date for a set of articles in one go, and so forth. The example custom bulk action implementation described below is added in a new XperienCentral plugin. It is also possible to add a custom bulk action to an existing plugin.

The bulk actions available in XperienCentral by default are delete and export. The delete bulk action is available out-of-the-box and the export bulk action is added when you install the Connector API add-on. Bulk actions are accessible in the Actions menu in Advanced Search:

[screengrab]

Bulk Action Factory Implementation

Each bulk action is identified by an action type. This is a unique ID string such as "exportToExcel". Every instance of a bulk action type is created by a custom bulk action factory. The implementation of this custom bulk action factory is also responsible for registering the custom bulk action in XperienCentral. Every custom bulk action factory must extend the class BulkActionFactory. For example:


Code Block
themeEclipse
Code Block
themeEclipse
import nl.gx.webmanager.services.credentials.CredentialsProviderService;
.jaxrs.search.bulkactions.api.BulkActionFactory;
public interface ExportToExcelFactory extends BulkActionFactory {
}


The registering and unregistering of a bulk action is performed using the onStart and onStop methods of the factory implementation via the bulk action service injected by OSGi:


Code Block
themeEclipse

public class ExampleCredentialsProviderServiceImplExportToExcelBulkActionFactoryImpl extends SimpleServiceComponent implements ExampleCredentialsProviderServiceExportToExcelFactory {

	protected   private static final LoggerString LOGACTION_TYPE = "exportToExcel";

   // Injected by OSGI
   private ExcelService myExcelService;
   private BulkActionService myBulkActionService;

   @Override
   public void onStart() {
      super.onStart();
      myBulkActionService.addBulkActionFactory(ACTION_TYPE, this);
   }

   @Override
   public void onStop() {
      super.onStop();
      myBulkActionService.removeBulkActionFactory(ACTION_TYPE);
   }


The Actions drop-down menu in Advanced Search is populated with labels provided by all bulk action factories registered to the bulk action service. The method getActionLabel must be overridden in your factory:


Code Block
themeEclipse
@Override
public String getActionLabel(String locale) {
   String name = myNameDescriptions.get(locale);
   if (name == null) {
      name = myNameDescriptions.get(LOCALE_EN);
   }
   return name;
}


Instantiating a bulk action is accomplished by calling the createBulkActionInstance method. You should override this method in your custom factory to return the correct bulk action type:


Code Block
themeEclipse
@Override
public BulkAction createBulkActionInstance(String actionIdentifier, List<String> items,FrameworkDependencies frameworkDependencies, String locale) {
   return new ExportToExcelBulkAction(actionIdentifier, items, frameworkDependencies, locale, myExcelService);
}

A method call to createBulkActionInstance will trigger the creation of a new bulk action of type custom with possibly its own parameters such as an Excel service like that shown in the example above. This method call will usually come from the bulk action handling mechanism in XperienCentral.



...

Bulk Action Implementation

To implement the bulk action itself, your bulk action class must extend AbstractBulkAction and define the members and methods it needs for its custom functionality:


Code Block
themeEclipse
public class ExportToExcelBulkAction extends AbstractBulkAction {
   private ExcelService myExcelService;
   private ConfigurationManagement myConfigurationManagement;

The constructor of your class should look like this:


Code Block
themeEclipse
public ExportToExcelBulkAction(String actionIdentifier, List<String> items, FrameworkDependencies frameworkDependencies, String locale, ExcelService excelService) {
   super(actionIdentifier, items, frameworkDependencies, locale);
   myConfigurationManagement = frameworkDependencies.getConfigurationManagement();
   myExcelService = excelService;


The actionIdentifier parameter is the actionType identifier string. The items parameter is a list of item identifiers like "articleversion-14" that were selected for the bulk action.  The frameWorkDependencies parameter is a collection of services in the framework that are made available for use in your bulk action by the bulk action service. The locale parameter is the locale of the content editor. All these parameters are always passed on by the bulk action service when creating a bulk action. The last parameter, excelService, is a custom parameter used in this example. The call to the super constructor ensures that the bulk action is initialized properly and that the bulk action framework can handle the action.

Next, the actual validation code and business logic for the bulk action needs to be implemented. This is done using the methods checkPermissions, preProcess, treat and postProcess. The method checkPermissions is called after the bulk action selection is made in order to check whether the bulk action can be executed on the selected items. The method is called for every item in the selection. To only allow the action to be executed on items with sufficient edit permissions, you could implement the method like this:

Code Block
themeEclipse
@Override
public ProgressResponseBean.MsgType checkPermissions(BulkContentItemVersion item) {
   return item.checkEditPermissions();
}


The method treat is called in order to execute the actual action on one content item in the bulk action. The bulk action framework will iterate over the selection of items and call this method for every item. An implementation of a bulk action performing a state change on every content item of the selection, for example to publish an entire content set at once, could be implemented as follows:


Code Block
themeEclipse
@Override
public void treat(BulkContentItemVersion bulkContentItemVersion) throws OperationFailedException {
   LOG.log(Level.FINE, "Changing status of item {0} to {1}", new Object[] { bulkContentItemVersion.getTitle(), myWorkflowType.getWorkflowName()});

   ContentItemVersion<?> contentItemVersion = getContentItemVersion(bulkContentItemVersion);
   List<WorkflowModelState> targetWorkflowModelStates = getWorkflowModelStates(contentItemVersion);

   if (targetWorkflowModelStates.size() == 1) {
      try {
         WorkflowUtils.bringToState(contentItemVersion, targetWorkflowModelStates.get(0), myWorkflowService);
      } catch (UnExecutableWorkflowActionException e) {
         throw new OperationFailedException("Failed to change state of " + bulkContentItemVersion.getType() + WITH_ID + bulkContentItemVersion.getId());
      }
   } else if (targetWorkflowModelStates.size() > 1) {
      throw new OperationFailedException("Multiple target WorkflowModelStates found for " + bulkContentItemVersion.getType() + WITH_ID + bulkContentItemVersion.getId());
   } else {
      throw new OperationFailedException("No target WorkflowModelState found for mediaitemversion " + bulkContentItemVersion.getType() + WITH_ID + bulkContentItemVersion.getId());
   }
}


The method preProcess can be overridden in order to prepare and initialize the bulk action. The method postProcess can be overridden in order to perform additional work after all content items are processed. These two methods together with the treat method can be used to export data to Excel, for example:


Code Block
themeEclipse
@Override
public void preProcess() {
   pages = new ArrayList<>();
   2contentItems = new ArrayList<>();
}

@Override
public void treat(BulkContentItemVersion item) {
   LOG.log(Level.FINE, "Exporting data of item {0}", new Object[] { item.getTitle() });
   if (item instanceof BulkContentItemPageVersion) {
      pages.add(item.getId());
   } else if (item instanceof BulkContentItemMediaItemVersion) {
      contentItems.add(item.getId());
   }
}

@Override
public void postProcess(ProgressResponseBean bean) {
   String finishMessage = "";
   File excelFile = null;
   if (checkExportDirectory()) {
      excelFile = myExcelService.createExcel(pages, contentItems, myFilePath);
   }
   if (excelFile != null && excelFile.isFile()) {
      finishMessage = "<br /><a href=\"" + myExportDownloadUrl + "\" download>Download het export Excel bestand</a>.";
   } else {
      finishMessage = "Error";
   }
   bean.finishMessage = finishMessage;
}

Defining the Bulk Action Factory Component

The bulk action factory is a service bundle and must be defined as such in the Activator class of your plugin. Its definition will look something like this:


Code Block
themeEclipse
private ServiceComponentDefinitionImpl getExportToExcelFactoryComponent() {
   ServiceComponentDefinitionImpl definition = new ServiceComponentDefinitionImpl(false);
definition.setId(WCBConstants.EXPORT_TO_EXCEL_FACTORY_COMPONENT_ID);
definition.setName(WCBConstants.EXPORT_TO_EXCEL_FACTORY_COMPONENT_NAME);
definition.setDescription(WCBConstants.EXPORT_TO_EXCEL_FACTORY_COMPONENT_DESCRIPTION);
definition.setTypeId(ServiceComponentType.class.getName());
definition.setProperties(new Hashtable<>());
definition.setImplementationClassName(ExportToExcelBulkActionFactoryImpl.class.getName());
definition.setInterfaceClassNames(new String[]{ExportToExcelFactory.class.getName()});

setRequiredDependencies(definition, ExcelService.class, BulkActionService.class);
   return definition;
}


Back to top


...

Credentials Service Provider


Panel
borderColor#0081C0
titleColor#0081C0

The following applies to XperienCentral versions 10.19 and higher.


In a standard installation, XperienCentral users are managed in the Authorization component. Along with the Authorization component, there are two types of credentials sets that are not targeted at the editorial tasks but are infrastructure-related. By default these credential sets are managed by the following properties in the XperienCentral Setup Tool (General (R30 and older) tab):

These credentials sets are for:

  • internal_http_authentication_password / internal_http_authentication_username needed for internal_http_use_authentication or internal_http_use_form_authentication
  • http_proxy_username / http_proxy_password needed for http_use_proxy

Because these credentials sets can be managed in an organizational credential storage system external to XperienCentral, you can create an integration with this thereby removing the need to fill in these credential properties in the Setup Tool.

This can be accomplished via your own integration component by creating a plugin that implements the Credentials Service Provider. When there is no plugin active that implements the credentials services, a look-up is done on the settings internal_http_use_authentication or internal_http_use_form_authentication in the "application_settings" section in the General (R30 and older) tab of the Setup Tool. If a plugin is active that implements the XperienCentral credentials service, the username/password combination from the plugin is used to authorize the user.

When a credentials provider plugin is created, the service in the activator needs to implement the CredentialsProviderService and CredentialsProvider classes. The following is an example implementation of a credential provider.


Code Block
themeEclipse
import nl.gx.webmanager.services.credentials.CredentialsProviderService;

public class ExampleCredentialsProviderServiceImpl extends SimpleServiceComponent implements ExampleCredentialsProviderService {

	protected static final Logger LOG = Logger.getLogger(ExampleCredentialsProviderServiceImpl.class.getName());
	private final static String USERNAME = "test";
	private final static String PASSWORD = "test";

	/**
	 * {@inheritDoc}
	 */
	public Credentials getCredentials(String identifier) {
		LOG.log(Level.INFO,"Returning username/password test/test as example credentials for identifier '" + identifier + "'");
		if (identifier.equalsIgnoreCase(CredentialsProviderService.INTERNAL_HTTP_REQUESTS_CREDENTIALS_IDENTIFIER) {
			// Integrate here with some type of storage for credential sets. For now..
			return new CredentialsImpl(identifier, USERNAME, PASSWORD);
		} else if (identifier.equalsIgnoreCase(CredentialsProviderService.HTTP_PROXY_CREDENTIALS_IDENTIFIER) {
			// Integrate here with some type of storage for credential sets. For now..
			return new CredentialsImpl(identifier, USERNAME, PASSWORD);
		} else {
			// For credentials used in custom configuration sets you can identity your own identifiers and capture
 			// them here. For now..
			return new CredentialsImpl(identifier, USERNAME, PASSWORD);
		}
	}
}


// The CredentialsImpl used
 
import nl.gx.webmanager.services.credentials.UsernamePasswordCredentials;
 
/**
 * Simple POJO implementation of the {@link UsernamePasswordCredentials} interface.
 */
public class CredentialsImpl implements UsernamePasswordCredentials {
     private String  myId;
     private String  myUsername;
     private String  myPassword;Logger.getLogger(ExampleCredentialsProviderServiceImpl.class.getName());
	private final static String USERNAME = "test";
	private final static String PASSWORD = "test";

	/**
	 * {@inheritDoc}
	 */
	public Credentials getCredentials(String identifier) {
		LOG.log(Level.INFO,"Returning username/password test/test as example credentials for identifier '" + identifier + "'");
		if (identifier.equalsIgnoreCase(CredentialsProviderService.INTERNAL_HTTP_REQUESTS_CREDENTIALS_IDENTIFIER) {
			// Integrate here with some type of storage for credential sets. For now..
			return new CredentialsImpl(identifier, USERNAME, PASSWORD);
		} else if (identifier.equalsIgnoreCase(CredentialsProviderService.HTTP_PROXY_CREDENTIALS_IDENTIFIER) {
			// Integrate here with some type of storage for credential sets. For now..
			return new CredentialsImpl(identifier, USERNAME, PASSWORD);
		} else {
			// For credentials used in custom configuration sets you can identity your own identifiers and capture
 			// them here. For now..
			return new CredentialsImpl(identifier, USERNAME, PASSWORD);
		}
	}
}


// The CredentialsImpl used
 
import nl.gx.webmanager.services.credentials.UsernamePasswordCredentials;
 
/**
 * Simple POJO implementation of the {@link UsernamePasswordCredentials} interface.
 */
public class CredentialsImpl implements UsernamePasswordCredentials {
     private String  myId;
     private String  myUsername;
     private String  myPassword;
 
    /**
     * Creates a new credentials object.
     *
     * @param username The username.
     * @param password The password.
     */
 
    public CredentialsImpl(String id, String username, String password) {
        myId = id;
        myUsername = username;
        myPassword = password;
    }
 
    /**
     * {@inheritDoc}
     */
    public String getId() {
        return myId;
    }
 
    /**
     * {@inheritDoc}
     */
    public String getUsername() {
        return myUsername;
    }
 
    /**
     * {@inheritDoc}Creates a new credentials object.
     */
    public String getPassword() { * @param username The username.
     * @param password returnThe myPassword;password.
     }*/
 
     /**
public CredentialsImpl(String id, String username, String password) {
       * ReturnsmyId the= Stringid;
 represenation of a credentials object.
   myUsername = *username;
     *  @return ThemyPassword String= representation.password;
    }
 */
    @Override/**
    public String toString()* {@inheritDoc}
     */
   return "id=" +public String getId() +{
 ", username=" + getUsername() + ", password=" + getPassword();
    }
}

Note that other credential sets that are infrastructure-related that are not already covered via the ones above can also be run though the credential provider logic, however in this case you need to accommodate this yourself. For example:

Code Block
languagejava
themeEclipse
import nl.gx.webmanager.services.credentials.CredentialsProviderService;

public class SomeClassThatNeedsTheCustomCredentialsSet {
	....
return myId;
    }
 
   OwnedConfigurationService myOwnedConfigurationService;

	private final static String CREDENTIAL_IDENTIFIER = "someCredentialSetIdentier";

	public void doSomething() {
		String username, password;
		CustomCredenentials someCustomCredentials = myCredentialsProviderService.getCredentials(CREDENTIAL_IDENTIFIER);

		if (someCredentials == null) {
			username = myOwnedConfigurationService.getUsername();	
			password = myOwnedConfigurationService.getPassword();
		} else {
			username = someCustomCredentials.getUsername();	
			password = someCustomCredentials.getPassword();
		}
		// Use the credentials
		....
	}
}		

Depending on the application server being used, a credential provider or -vault may be standard within the application server such as inside IBM WebSphere or another product like CyberArk can be used for this functionality (in case of Tomcat or Jboss).

 Back to Top

Property Editors

Property Editors are used to facilitate conversions between a complex type and String. Posted values from an HTML form will usually be converted to a String by the servlet engine. Spring MVC offers a way to convert incoming Strings to complex types; it also converts complex types to Strings when the response is sent back to the browser. Spring MVC offers this functionality through the Property Editors. The Spring MVC and the XperienCentral platforms already have a few default Property Editors for the following types:

  • All primitive types plus their auto-boxed equivalents;
  • Date (in the format of “dd-MM-yyyy” or “dd/MM/yyyy”).

To add a custom Property Editor, follow these steps:

  1. Implement a custom Property Editor.
  2. Register the custom Property Editor in the initBinder method.

In the following parts of this topic, an example of a custom Property Editor is shown based on the Calendar type. By default, there is no support for a property of type Calendar although it might be of interest for plugin developers.

Implement the Custom Property Editor

To implement a custom Property Editor, a class is needed that:

  • Implements the PropertyEditor interface; usually this is done by extending the PropertyEditorSupport class;
  • Implements the methods setAsText and getAsText.

The implementation of the CustomCalendarEditor looks like this:

Code Block
themeEclipse
public class CustomCalendarEditor extends PropertyEditorSupport {
	private final DateFormat myDateFormat;
	private final static Logger LOG =
Logger.getLogger(CustomCalendarEditor.class.getName());

	public CustomCalendarEditor(DateFormat dateFormat) {
		myDateFormat = dateFormat;
	}
	public void setAsText(String text) {
		if (text == null) {
			setValue(null);
		} else {
			try {
				myDateFormat.parse(text);
				setValue(myDateFormat.getCalendar());
			} catch (ParseException ex) {
				LOG.log(Level.WARNING, "Could not parse the date '" + text + "'" + "\nThe exception\n: " + ex);
			}
		}
	}

	public String getAsText() {
		Calendar value = (Calendar) getValue();
		if (value == null) {
			return "";
		} else {
			return myDateFormat.format(value.getTime());
		}
	}
}

Register the Custom Property Editor

 /**
     * {@inheritDoc}
     */
    public String getUsername() {
        return myUsername;
    }
 
    /**
     * {@inheritDoc}
     */
    public String getPassword() {
        return myPassword;
    }
 
    /**
     * Returns the String represenation of a credentials object.
     *
     * @return The String representation.
     */
    @Override
    public String toString() {
        return "id=" + getId() + ", username=" + getUsername() + ", password=" + getPassword();
    }
}


Note that other credential sets that are infrastructure-related that are not already covered via the ones above can also be run though the credential provider logic, however in this case you need to accommodate this yourself. For example:


Code Block
languagejava
themeEclipse
import nl.gx.webmanager.services.credentials.CredentialsProviderService;

public class SomeClassThatNeedsTheCustomCredentialsSet {
	....

   OwnedConfigurationService myOwnedConfigurationService;

	private final static String CREDENTIAL_IDENTIFIER = "someCredentialSetIdentier";

	public void doSomething() {
		String username, password;
		CustomCredenentials someCustomCredentials = myCredentialsProviderService.getCredentials(CREDENTIAL_IDENTIFIER);

		if (someCredentials == null) {
			username = myOwnedConfigurationService.getUsername();	
			password = myOwnedConfigurationService.getPassword();
		} else {
			username = someCustomCredentials.getUsername();	
			password = someCustomCredentials.getPassword();
		}
		// Use the credentials
		....
	}
}		


Depending on the application server being used, a credential provider or -vault may be standard within the application server such as inside IBM WebSphere or another product like CyberArk can be used for this functionality (in case of Tomcat or Jboss).


 Back to Top


...

Property Editors

Property Editors are used to facilitate conversions between a complex type and String. Posted values from an HTML form will usually be converted to a String by the servlet engine. Spring MVC offers a way to convert incoming Strings to complex types; it also converts complex types to Strings when the response is sent back to the browser. Spring MVC offers this functionality through the Property Editors. The Spring MVC and the XperienCentral platforms already have a few default Property Editors for the following types:

  • All primitive types plus their auto-boxed equivalents;
  • Date (in the format of “dd-MM-yyyy” or “dd/MM/yyyy”).

To add a custom Property Editor, follow these steps:

  1. Implement a custom Property Editor.
  2. Register the custom Property Editor in the initBinder method.

In the following parts of this topic, an example of a custom Property Editor is shown based on the Calendar type. By default, there is no support for a property of type Calendar although it might be of interest for plugin developers.

Implement the Custom Property Editor

To implement a custom Property Editor, a class is needed that:

  • Implements the PropertyEditor interface; usually this is done by extending the PropertyEditorSupport class;
  • Implements the methods setAsText and getAsText.

The implementation of the CustomCalendarEditor Now that the custom Property Editor is implemented, it has to be registered with Spring MVC. This is done in the initBinder method; this method is placed in the controller of your component (for example, CustomElementController.java). The registration looks like this:


Code Block
themeEclipse
@Override
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
super.initBinder(request, binder);
if (myValidator == null) {
		myValidator = new CustomCalendarEditor(
new SimpleDateFormat("dd/MM/yyyy");
addValidator(myValidator);
}
}

The Custom Property Editor in Action

Once the custom Property Editor has been created and registered, it is ready to use. This means that it is now possible to use the Calendar type for properties in the Java code. Below are some code snippets that illustrate the use of the custom the Calendar type Property Editor that was created in the above example.

Code in the edit-JSP (e.g. editCustomElement.jspf):

Code Block
themeEclipse
 <fmt:message key="helloworldelement.inputfieldLabel.birthdate" />:
Code Block
themeEclipse
<wmedit:datePicker path="birthdate"/>

Code in the FormBacking object (for example,  CustomElementFBO.java):

Code Block
themeEclipse
private Calendar myBirthdate;

public Calendar getBirthdate() {
	return myBirthdate;
}

public void setBirthdate(Calendar bdate) {
	myBirthdate = bdate;
}

The result as seen in XperienCentral:

Image Removed

Back to Top

Validators

Validators can be used to validate user input and to generate client side error messages when invalid input is provided by a user. Validators prevent users from entering invalid data. Validators are Java classes that implement the org.springframework.validation.Validator interface. This interface provides two methods:

Code Block
themeEclipse
boolean supports(Class clazz);
void validate(Object target, Errors errors);

The supports method indicates which classes the validators can handle. The validate method performs the actual validation. The Sprint MVC framework provides API methods that make it easy to perform this validation, for example org.springframework.validation.ValidationUtils and org.springframework.validation.Errors.

To register the validator it must be added in the initBinder method of the controller using the addValidator method. You should only add the validator once per lifetime of the controller instance. So if you have a stateful controller (which is the default), be sure to instantiate and register the validator only once. If you unconditionally add the validator in the initBinder method, the validator will be invoked twice on the second HTTP request, three times on the third, and so forth.

public class CustomCalendarEditor extends PropertyEditorSupport {
	private final DateFormat myDateFormat;
	private final static Logger LOG =
Logger.getLogger(CustomCalendarEditor.class.getName());

	public CustomCalendarEditor(DateFormat dateFormat) {
		myDateFormat = dateFormat;
	}
	public void setAsText(String text) {
		if (text == null) {
			setValue(null);
		} else {
			try {
				myDateFormat.parse(text);
				setValue(myDateFormat.getCalendar());
			} catch (ParseException ex) {
				LOG.log(Level.WARNING, "Could not parse the date '" + text + "'" + "\nThe exception\n: " + ex);
			}
		}
	}

	public String getAsText() {
		Calendar value = (Calendar) getValue();
		if (value == null) {
			return "";
		} else {
			return myDateFormat.format(value.getTime());
		}
	}
}

Register the Custom Property Editor

Now that the custom Property Editor is implemented, it has to be registered with Spring MVC. This is done in the initBinder method; this method is placed in the controller of your component (for example, CustomElementController.java). The registration looks like thisThe code snippets below provide an example of using a text validator for a custom element that rejects any empty text value or value that equals “not empty”:


Code Block
themeEclipse
@Override
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
	super.initBinder(request, binder);
if (myValidator == null) {
		myValidator = new TextValidator();
		addValidator(myValidator);
	}
}

The validator class:

Code Block
themeEclipse
public class TextValidator implements Validator {

	public void validate(Object target, Errors errors) {
		CustomTextElementFBO element = (CustomTextElementFBO) target;
		ValidationUtils.rejectIfEmpty(errors, "text", "maynotbeempty");
		if (element.getText() != null 
			&& element.getText().equals("not empty")) {
			errors.rejectValue("text", "maynotbenotempty");
		}
	}

	public boolean supports(Class clazz) {
		if (CustomElementFBO.class.isAssignableFrom(clazz)) {
			return true;
		}
		else {
			return false;
		}
	}
}

In messages_en_US.properties:

Code Block
themeEclipse
maynotbeempty=Text may not be empty
maynotbenotempty=Text may not be “not empty”

As a result the message “Text may not be empty” will be displayed if the user input was invalid:

Image Removed

Back to Top

HTTP Client

 CustomCalendarEditor(
new SimpleDateFormat("dd/MM/yyyy");
addValidator(myValidator);
}
}

The Custom Property Editor in Action

Once the custom Property Editor has been created and registered, it is ready to use. This means that it is now possible to use the Calendar type for properties in the Java code. Below are some code snippets that illustrate the use of the custom the Calendar type Property Editor that was created in the above example.

Code in the edit-JSP (e.g. editCustomElement.jspf):


Code Block
themeEclipse
 <fmt:message key="helloworldelement.inputfieldLabel.birthdate" />:


Code Block
themeEclipse
<wmedit:datePicker path="birthdate"/>


Code in the FormBacking object (for example,  CustomElementFBO.java):


Code Block
themeEclipse
private Calendar myBirthdate;

public Calendar getBirthdate() {
	return myBirthdate;
}

public void setBirthdate(Calendar bdate) {
	myBirthdate = bdate;
}


The result as seen in XperienCentral:


Image Added


Back to Top


...

Validators

Validators can be used to validate user input and to generate client side error messages when invalid input is provided by a user. Validators prevent users from entering invalid data. Validators are Java classes that implement the org.springframework.validation.Validator interface. This interface provides two methodsThe java.net package part of the Java API provides a basic set of classes which can be used to handle HTTP GET and POST requests. However, in some cases a more powerful HTTP client API is needed. The commons HTTP client (org.apache.commons.httpclient.HttpClient) may be a better alternative for those cases.To incorporate the commons HTTPp client jar files into a plugin, modify the pom.xml file in order to define a dependency with the commons HTTPclient artifact. For example, define the following dependency:


Code Block
themeEclipse
<dependency>
	<groupId>commons-httpclient</groupId>
	<artifactId>commons-httpclient</artifactId>
	<version>3.0</version>
</dependency>

Without further changes however, the plugin will throw a runtime error upon the invocation of the HTTP client because of a conflict in commons logging:

Invalid class loader hierarchy. You have more than one version of 'org.apache.commons.logging.Log' visible, which is not allowed.

The reason for this is that the dependency above will cause the commons-logging artifact to be included in the plugin since it is required by the HTTP client artifact. However, the XperienCentral framework also exports the commons logging package which causes the mismatch.

boolean supports(Class clazz);
void validate(Object target, Errors errors);


The supports method indicates which classes the validators can handle. The validate method performs the actual validation. The Sprint MVC framework provides API methods that make it easy to perform this validation, for example org.springframework.validation.ValidationUtils and org.springframework.validation.Errors.

To register the validator it must be added in the initBinder method of the controller using the addValidator method. You should only add the validator once per lifetime of the controller instance. So if you have a stateful controller (which is the default), be sure to instantiate and register the validator only once. If you unconditionally add the validator in the initBinder method, the validator will be invoked twice on the second HTTP request, three times on the third, and so forth.

The code snippets below provide an example of using a text validator for a custom element that rejects any empty text value or value that equals “not empty”In order to resolve this issue, an additional dependency with the commons-logging artifact must be defined with a scope provided so that at runtime the commons-logging classes exported by the framework will be used instead:


Code Block
themeEclipse
<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.0.4</version>
	<scope>provided</scope>
</dependency>

...

public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
	if (myValidator == null) {
		myValidator = new TextValidator();
		addValidator(myValidator);
	}
}


The validator class:


Code Block
themeEclipse
private staticpublic class TextValidator implements Validator {

	public void configureHttpClient2Proxyvalidate(HttpClientObject httpClienttarget, HttpMethodErrors methoderrors) {
		CustomTextElementFBO element = (CustomTextElementFBO) target;
	String proxyHost = System.getProperty("http.proxyHost		ValidationUtils.rejectIfEmpty(errors, "text", "maynotbeempty");
		if (element.getText() != null 
			&& element.getText().equals("not empty")) {
			errors.rejectValue("text", "maynotbenotempty");
	if (proxyHost == null || "".equals(proxyHost	}
	}

	public boolean supports(Class clazz) {
		if (CustomElementFBO.class.isAssignableFrom(clazz)) {
			return true;
		}
	try	else {
		org.apache.commons.httpclient.URI apacheUri = method.getURI();
		java.net.URI javaUri = null;
		if (apacheUri.isAbsoluteURI()==false) {
			javaUri = new 
			java.net.URI(httpClient.getHostConfiguration().getHostURL());
	} else {
		javaUri = new java.net.URI(apacheUri.toString());
	}
	// ProxySelector
	List<Proxy> selectedProxy = 
	ProxySelector.getDefault().select(javaUri);
	if (selectedProxy.size()==0 || 
	selectedProxy.get(0).type()==Proxy.Type.DIRECT) {
		// No proxy needed.
		return;
	}
	HostConfiguration hc = httpClient.getHostConfiguration();
	hc.setHost(javaUri.getHost(), javaUri.getPort(), 
	Protocol.getProtocol(javaUri.getScheme()));
	Proxy proxy = selectedProxy.get(0);
	InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
	hc.setProxy(proxyAddress.getHostName(),proxyAddress.getPort());
	if (System.getProperty("http.proxyUserName") != null) {
		httpClient.getState().setProxyCredentials(null, null,
			new UsernamePasswordCredentials(System.getProperty("http.proxyUserName"), System.getProperty("http.proxyPassword")));
		httpClient.getState().setAuthenticationPreemptive(true);
	}


  } catch (URISyntaxException ex) {
		LOG.log(Level.WARNING, null, ex);
  } catch (URIException ex) {
		lOG.log(Level.WARNING, null, ex);
  }
}

Back to Top

...

	return false;
		}
	}
}


In messages_en_US.properties:


Code Block
themeEclipse
maynotbeempty=Text may not be empty
maynotbenotempty=Text may not be “not empty”


As a result the message “Text may not be empty” will be displayed if the user input was invalid:


Image Added


Back to Top


...

HTTP Client


The java.net package part of the Java API provides a basic set of classes which can be used to handle HTTP GET and POST requests. However, in some cases a more powerful HTTP client API is needed. The commons HTTP client (org.apache.commons.httpclient.HttpClient) may be a better alternative for those cases.

To incorporate the commons HTTP client jar files into a plugin, modify the pom.xml file in order to define a dependency with the commons HTTPclient artifact. For example, define the following dependency:


Code Block
themeEclipse
<dependency>
	<groupId>commons-httpclient</groupId>
	<artifactId>commons-httpclient</artifactId>
	<version>3.0</version>
</dependency>


Without further changes however, the plugin will throw a runtime error upon the invocation of the HTTP client because of a conflict in commons logging:

Invalid class loader hierarchy. You have more than one version of 'org.apache.commons.logging.Log' visible, which is not allowed.

The reason for this is that the dependency above will cause the commons-logging artifact to be included in the plugin since it is required by the HTTP client artifact. However, the XperienCentral framework also exports the commons logging package which causes the mismatch.

In order to resolve this issue, an additional dependency with the commons-logging artifact must be defined with a scope provided so that at runtime the commons-logging classes exported by the framework will be used instead:


Code Block
themeEclipse
<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.0.4</version>
	<scope>provided</scope>
</dependency>


XperienCentral offers standard HTTP proxy configuration settings. These settings are configured using the XperienCentral Setup Tool (/web/setup). The settings are made available through the default system networking properties. For complete information about these properties, go to the URL http://docs.oracle.com/javase/8/. The example below shows how you can apply these settings in XperienCentral in combination with HTTP clients version 2 and 3.


Code Block
themeEclipse
private static void configureHttpClient2Proxy(HttpClient httpClient, HttpMethod method) {
	String proxyHost = System.getProperty("http.proxyHost");
	if (proxyHost == null || "".equals(proxyHost)) {
		return;
	}
	try {
		org.apache.commons.httpclient.URI apacheUri = method.getURI();
		java.net.URI javaUri = null;
		if (apacheUri.isAbsoluteURI()==false) {
			javaUri = new 
			java.net.URI(httpClient.getHostConfiguration().getHostURL());
	} else {
		javaUri = new java.net.URI(apacheUri.toString());
	}
	// ProxySelector
	List<Proxy> selectedProxy = 
	ProxySelector.getDefault().select(javaUri);
	if (selectedProxy.size()==0 || 
	selectedProxy.get(0).type()==Proxy.Type.DIRECT) {
		// No proxy needed.
		return;
	}
	HostConfiguration hc = httpClient.getHostConfiguration();
	hc.setHost(javaUri.getHost(), javaUri.getPort(), 
	Protocol.getProtocol(javaUri.getScheme()));
	Proxy proxy = selectedProxy.get(0);
	InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
	hc.setProxy(proxyAddress.getHostName(),proxyAddress.getPort());
	if (System.getProperty("http.proxyUserName") != null) {
		httpClient.getState().setProxyCredentials(null, null,
			new UsernamePasswordCredentials(System.getProperty("http.proxyUserName"), System.getProperty("http.proxyPassword")));
		httpClient.getState().setAuthenticationPreemptive(true);
	}


  } catch (URISyntaxException ex) {
		LOG.log(Level.WARNING, null, ex);
  } catch (URIException ex) {
		lOG.log(Level.WARNING, null, ex);
  

The XperienCentral Performance Dashboard provides detailed information about how the parts of XperienCentral's infrastructure are functioning. The Performance Dashboard measures the speed and response time of page requests and internal queries as well as other settings that affect how XperienCentral is performing and rates the results according to the optimal expected results. The Performance Dashboard plugin is extensible, which allows you to create plugins that add custom performance indicators. Any custom indicators you create appear on a special tab named “Custom System Performance Indicators”.

Use the methods in the following classes to implement your custom performance indicators:

  • SystemHealthIndicator
  • SystemHealthIndicator.ValueStatus
Note
  • The indicators are called “System health” indicators instead of “Performance indicators”. The reason for this is that although the current focus is on implementing performance indicators, XperienCentral intends to support more generic system health indicators in the future.
  • For complete information on the methods in these classes, see the XperienCentral API Javadoc. For complete information about plugin extensibility, see Extensibility.

The following sample adds a category named “My Category” containing an indicator named “test custom PI” to the “Custom System Performance Indicators” tab.

Code Block
themeEclipse
public class CustomServiceImpl extends SimpleServiceComponent implements SystemHealthIndicatorExtensionPoint {
	// Private logger for this class
	private static final Logger LOG = Logger.getLogger(CustomServiceImpl.class.getName());
	public SystemHealthIndicator[] getPerformanceIndicators() {
	return new SystemHealthIndicator[] {new CustomPI()};
	}

	class CustomPI implements SystemHealthIndicator{
		public String getId() {return "testPI";};
		public String getCategory() {return "my category";}
		public String getType() {return TYPE_CUSTOM;};
		public String getName(Language language) {return "test custom PI";}
		public Object getValue() {return myCache.getCacheRatio;}
		public ValueStatus getValueState() {return myValueStatus;}
		public String getMessage(Language language) {return "test custom PI";}
		public void reset() {}
	}
}


Back to Top


...

Creating an Extension for the User Profiles Component

...