package org.sakaiproject.tool.assessment.shared.impl.assessment;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.RegisteredSecureDeliveryModuleIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.SecureDeliveryModuleIfc;
import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
/**
*
* @author Luis Camargo (lcamargo@respondus.com)
*
*/
public class SecureDeliveryServiceImpl implements SecureDeliveryServiceAPI {
private static Log log = LogFactory.getLog( SecureDeliveryServiceImpl.class );
/*
* Implementation of the SecureDeliveryModuleIfc interface with name,id ordering, except for id=NONE_ID
* which is always placed before any other
*/
private class SecureDeliveryModuleImpl implements RegisteredSecureDeliveryModuleIfc, Comparable<RegisteredSecureDeliveryModuleIfc> {
private String id;
private String name;
private boolean enabled;
public String getId() { return id; }
public String getName() { return name; }
public boolean isEnabled() { return enabled; }
public int compareTo(RegisteredSecureDeliveryModuleIfc other) {
if ( SecureDeliveryServiceAPI.NONE_ID.equals( id ) )
return -1;
else if ( SecureDeliveryServiceAPI.NONE_ID.equals( other.getId() ) )
return 1;
else if ( ! name.equals( other.getName() ) )
return name.compareTo( other.getName() );
else
return id.compareTo( other.getId() );
}
public boolean equals(Object obj) {
if ( obj instanceof RegisteredSecureDeliveryModuleIfc )
return compareTo( (RegisteredSecureDeliveryModuleIfc) obj ) == 0;
else
return false;
}
}
private Map<String,SecureDeliveryModuleIfc> secureDeliveryModules = new HashMap<String,SecureDeliveryModuleIfc>();
/**
* Loads the secure delivery plugins defined by the samigo.secureDeliveryPlugins setting.
*
* samigo.secureDeliveryPlugins is a list of JAR files separated by ":". Each JAR file plugin can provide
* one or more secure module implementation(s).
*/
public void init() {
String secureDeliveryPluginSetting = ServerConfigurationService.getString( "samigo.secureDeliveryPlugins", null );
log.info( "Secure delivery plugins are: " + secureDeliveryPluginSetting );
if ( secureDeliveryPluginSetting != null ) {
String[] plugins = secureDeliveryPluginSetting.split( ":" );
for ( String plugin : plugins ) {
handlePlugin( plugin );
}
}
}
/**
* @returns true if at least one secure delivery module implementation is available.
*/
public boolean isSecureDeliveryAvaliable() {
return secureDeliveryModules.size() > 0;
}
/**
* @param moduleId
* @return true if the module with moduleId is availabe.
*/
public boolean isSecureDeliveryModuleAvailable(String moduleId) {
if ( NONE_ID.equals( moduleId ) )
return true;
return secureDeliveryModules.get( moduleId ) != null;
}
/**
* @return A set of RegisteredSecureDeliveryModuleIfc entries with the module name internationalized
* for the given locale.
*
* The list always includes NONE_ID as its first element. Others are ordered alphabetically by
* their name.
*/
public SortedSet<RegisteredSecureDeliveryModuleIfc> getSecureDeliveryModules( Locale locale ) {
SortedSet<RegisteredSecureDeliveryModuleIfc> moduleSet = new TreeSet<RegisteredSecureDeliveryModuleIfc>();
//ResourceBundle rb = ResourceBundle.getBundle("org.sakaiproject.tool.assessment.bundle.Messages", locale);
SecureDeliveryModuleImpl module = new SecureDeliveryModuleImpl();
module.id = NONE_ID;
//module.name = rb.getString( "none_secure_delivery_module" );
module.name = "None";
moduleSet.add( module );
for ( Map.Entry<String, SecureDeliveryModuleIfc> entry : secureDeliveryModules.entrySet() ) {
module = new SecureDeliveryModuleImpl();
module.id = entry.getKey();
module.name = entry.getValue().getModuleName( locale );
module.enabled = entry.getValue().isEnabled();
moduleSet.add( module );
}
return moduleSet;
}
/**
* @return The title decoration for the given module and locale. Returns empty string if moduleId is NONE_ID,
* null, not available or disabled.
*/
public String getTitleDecoration(String moduleId, Locale locale) {
SecureDeliveryModuleIfc module = secureDeliveryModules.get( moduleId );
if ( moduleId == null || NONE_ID.equals( moduleId ) || module == null || !module.isEnabled() )
return "";
try {
return module.getTitleDecoration( locale );
}
catch ( Exception e ) {
log.error( "getTitleDecoration failed for module " + moduleId, e);
return "";
}
}
/**
* Checks with the module specified by moduleId if the current delivery phase can continue. Returns
* SUCCESS if moduleId is null or NONE_ID or if the module is no longer available or disabled.
*
* @param moduleId
* @param phase
* @param assessment
* @param request
* @return
*/
public PhaseStatus validatePhase(String moduleId, Phase phase, PublishedAssessmentIfc assessment,HttpServletRequest request ) {
SecureDeliveryModuleIfc module = secureDeliveryModules.get( moduleId );
if ( moduleId == null || NONE_ID.equals( moduleId ) || module == null || !module.isEnabled() )
return PhaseStatus.SUCCESS;
try {
return module.validatePhase(phase, assessment, request );
}
catch ( Exception e ) {
log.error("canStartDelivery failed for module " + moduleId, e);
return PhaseStatus.SUCCESS;
}
}
/**
* Returns the initial HTML fragments for all active modules. The fragments are inserted into
* the assessment list.
*
* @param request
* @param locale
* @return
*/
public String getInitialHTMLFragments( HttpServletRequest request, Locale locale ) {
StringBuilder sb = new StringBuilder();
for ( SecureDeliveryModuleIfc module : secureDeliveryModules.values() ) {
if ( module.isEnabled() ) {
String fragment = module.getInitialHTMLFragment(request, locale);
if ( fragment != null && ! fragment.isEmpty() )
sb.append( fragment );
}
}
return sb.toString();
}
/**
* Returns an HTML appropriate for the combination of parameters. The fragment is injected
* during delivery. Returns empty string if module id is null or NONE_ID or if the module is no longer
* available or disabled.
*
* @param moduleId
* @param assessment
* @param request
* @param phase
* @param status
* @param locale
* @return
*/
public String getHTMLFragment(String moduleId, PublishedAssessmentIfc assessment, HttpServletRequest request, Phase phase, PhaseStatus status, Locale locale ) {
SecureDeliveryModuleIfc module = secureDeliveryModules.get( moduleId );
if ( moduleId == null || NONE_ID.equals( moduleId ) || module == null || !module.isEnabled() )
return "";
try {
return module.getHTMLFragment(assessment, request, phase, status, locale );
}
catch ( Exception e ) {
log.error( "getHTMLFragment failed for module " + moduleId, e);
return "";
}
}
/**
* Helper method to obtain a reference to the runtime instance of the module specified. The context object
* provided is passed to the module itself for validation and the reference is only returned if the module
* validation is successful. The idea is to let module developers interact with the mod
*
* How the actual context type and how it's validated is up to each module implementation.
*
* @param moduleId
* @param context
* @return the reference. null if the module is NONE_ID, not available or if the module rejected the context
*/
public SecureDeliveryModuleIfc getModuleReference( String moduleId, Object context ) {
SecureDeliveryModuleIfc module = secureDeliveryModules.get( moduleId );
if ( moduleId == null || NONE_ID.equals( moduleId ) || module == null )
return null;
try {
if ( module.validateContext( context ) )
return module;
else
return null;
}
catch ( Exception e ) {
log.error( "validateContext failed for module " + moduleId, e);
return null;
}
}
/**
* Uses the module specified to encrypt the exit password before storing it on the assessment settings. The
* encryption method used is up to the module implementation. Returns the same password if module id is null or
* NONE_ID or if the module is no longer available.
*
* @param moduleId
* @param password
* @return the encrypted password
*/
public String encryptPassword( String moduleId, String password ) {
SecureDeliveryModuleIfc module = secureDeliveryModules.get( moduleId );
if ( moduleId == null || NONE_ID.equals( moduleId ) || module == null )
return password;
try {
return module.encryptPassword( password );
}
catch ( Exception e ) {
log.error("encryptPassword failed for module " + moduleId, e);
return password;
}
}
/**
* Uses the module specified to decrypt the exit password. The encryption method used is up to the module
* implementation. Returns the same password if module id is null or NONE_ID or if the module is no longer
* available.
*
* @param moduleId
* @param password
* @return the plain text password
*/
public String decryptPassword( String moduleId, String password ) {
SecureDeliveryModuleIfc module = secureDeliveryModules.get( moduleId );
if ( moduleId == null || password == null || NONE_ID.equals( moduleId ) || module == null )
return password;
try {
return module.decryptPassword( password );
}
catch ( Exception e ) {
log.error("decryptPassword failed for module " + moduleId, e);
return password;
}
}
/**
* Looks for the spring-context.xml file on the plugin JAR and loads the beans that implement the
* SecureDeliveryModuleIfc interface
*
* @param secureDeliveryPlugin the path to the plugin JAR file
*/
private void handlePlugin( String secureDeliveryPlugin ) {
try
{
File file = new File( secureDeliveryPlugin );
if ( !file.exists() ) {
log.warn( "Secure delivery plugin " + secureDeliveryPlugin + " not found" );
return;
}
URL pluginUrl = new URL( "file:" + secureDeliveryPlugin );
URLClassLoader classLoader = new URLClassLoader( new URL[] { pluginUrl }, this.getClass().getClassLoader() );
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.setClassLoader( classLoader );
Resource resource = ctx.getResource( "jar:file:" + secureDeliveryPlugin + "!/spring-context.xml" );
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions( resource );
ctx.refresh();
String[] secureDeliveryModuleBeanNames = ctx.getBeanNamesForType( SecureDeliveryModuleIfc.class );
if ( secureDeliveryModuleBeanNames.length == 0 )
log.warn( "Secure delivery plugin doesn't define any beans of type SecureDeliveryModuleIfc" );
for ( String name : secureDeliveryModuleBeanNames ) {
SecureDeliveryModuleIfc secureDeliveryModuleBean = (SecureDeliveryModuleIfc) ctx.getBean( name );
log.info( "Loaded secure delivery module: " + secureDeliveryModuleBean + " (" + secureDeliveryModuleBean.getModuleName( Locale.getDefault() ) + ")" );
if ( secureDeliveryModuleBean.initialize() ) {
secureDeliveryModules.put( secureDeliveryModuleBean.getClass().getName(), secureDeliveryModuleBean );
}
}
}
catch ( Exception e ) {
log.error( "Unable to load secure delivery plugin " + secureDeliveryPlugin, e );
}
}
}