Index: lams_common/src/java/org/lamsfoundation/lams/tool/OutputDefinitionFactory.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/tool/OutputDefinitionFactory.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/tool/OutputDefinitionFactory.java (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -0,0 +1,223 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/* $Id$ */ +package org.lamsfoundation.lams.tool; + +import java.util.Locale; +import java.util.SortedMap; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.util.ILoadedMessageSourceService; +import org.lamsfoundation.lams.util.MessageService; + +import org.springframework.context.MessageSource; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * This class forms the basic implementation of an output definition factory, which is the class in a tool that + * creates the output definitions for a tool. Each tool that has outputs should create its own factory class that + * extends this class and uses the methods implemented in this class to create the actual ToolOutputDefinition objects. + * + * The implemented factory (in the tool) needs to supply either (a) its own messageService bean (a Spring bean normally + * defined in the tool's applicationContext file in the toolMessageService property or (b) the name of its I18N language package/filename AND + * the the loadedMessageSourceService (which is defined as a Spring bean in the core context). One of the two options (but not + * both) is required so that the getDescription() method can get the internationalised description of the output definition + * from the tool's internationalisation files. If neither the messageService or the I18N name is not supplied then the name + * if the output definition will appear in the description field. + * + * Example definitions for tool factories: + * + * + * org.lamsfoundation.lams.tool.mc.ApplicationResources + * + * + * + * + * + * + */ +public abstract class OutputDefinitionFactory { + + protected Logger log = Logger.getLogger(OutputDefinitionFactory.class); + + private MessageService toolMessageService; + + private ILoadedMessageSourceService loadedMessageSourceService; + private String languageFilename; + protected final String KEY_PREFIX = "output.desc."; + + /** Create a map of the tool output definitions, suitable for returning from the method + * getToolOutputDefinitions() in the ToolContentManager interface. The class for the toolContentObject + * will be unique to each tool e.g. for Multiple Choice tool it will be the McContent object. + * + * If the toolContentObject should not be null - if the toolContentId supplied in the call to + * getToolOutputDefinitions(Long toolContentId) does not match to any existing content, the content + * should be the tool's default tool content. + * + * @param toolContentObject + * @return SortedMap of ToolOutputDefinitions where the key is the name from each definition + * @throws ToolException + */ + public abstract SortedMap getToolOutputDefinitions(Object toolContentObject) throws ToolException; + + /** Tool specific toolMessageService, such as the forumMessageService in the Forum tool */ + public MessageService getToolMessageService() { + return toolMessageService; + } + + public void setToolMessageService(MessageService toolMessageService) { + this.toolMessageService = toolMessageService; + } + + + /** Set the common loadedMessageSourceService, based on the bean defined in the common Spring context. + * If toolMessageService is not set, then the languageFilename + * and loadedMessageSourceService should be set. */ + public ILoadedMessageSourceService getLoadedMessageSourceService() { + return loadedMessageSourceService; + } + + public void setLoadedMessageSourceService( + ILoadedMessageSourceService loadedMessageSourceService) { + this.loadedMessageSourceService = loadedMessageSourceService; + } + + /** Set the filename/path for the tool's I18N files. If toolMessageService is not set, then the languageFilename + * and loadedMessageSourceService should be set. */ + public String getLanguageFilename() { + return languageFilename; + } + + /** Get the filename and path for the tool's I18N files. */ + public void setLanguageFilename(String languageFilename) { + this.languageFilename = languageFilename; + } + + /** + * Get the I18N description for this definitionName. If the tool has supplied a messageService, then this + * is used to look up the key and hence get the description. Otherwise if the tool has supplied a I18N + * languageFilename then it is accessed via the shared toolActMessageService. If neither are supplied or + * the key is not found, then any "." in the name are converted to space and this is used as the description. + * + * The key must be in the format output.desc.[definition name]. For example a + * definition name of "learner.mark" becomes output.desc.learner.mark. + */ + protected String getDescription(String definitionName) { + MessageSource msgSource = null; + if ( getToolMessageService() != null ) { + msgSource = getToolMessageService().getMessageSource(); + } + if ( msgSource == null && getLoadedMessageSourceService() != null && getLanguageFilename() != null) { + msgSource = getLoadedMessageSourceService().getMessageService(getLanguageFilename()); + } + if ( msgSource == null ) { + log.warn("Unable to internationalise the description for the output definition "+definitionName+" as no MessageSource is available. "+ + "The tool's OutputDefinition factory needs to set either (a) messageSource or (b) loadedMessageSourceService and languageFilename."); + } + + String description = null; + if ( msgSource != null ) { + String key = KEY_PREFIX + definitionName; + Locale locale = LocaleContextHolder.getLocale(); + try { + description = msgSource.getMessage(key,null,locale); + } catch ( NoSuchMessageException e ) { + } + } + if ( description == null || description.length() == 0 ) { + description = definitionName.replace('.', ' '); + } + + return description; + } + + /** Generic method for building a tool output definition. It will get the definition's description + * from the I18N file using the getDescription() method. + * Only use if the other buildBlahDefinitions do not suit your needs. */ + protected ToolOutputDefinition buildDefinition(String definitionName, OutputType type, Object startValue, Object endValue, Object complexValue) { + ToolOutputDefinition definition = new ToolOutputDefinition(); + definition.setName(definitionName); + definition.setDescription(getDescription(definitionName)); + definition.setType(type); + definition.setStartValue( startValue ); + definition.setEndValue( endValue ); + definition.setComplexDefinition( complexValue ); + return definition; + } + + //The mark for a user's last attempt at answering the question(s). + /** Build a tool definition designed for a range of integer values. + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_LONG. */ + protected ToolOutputDefinition buildRangeDefinition(String definitionName, Long startValue, Long endValue) { + return buildDefinition(definitionName, OutputType.OUTPUT_LONG, startValue, endValue, null); + } + + /** Build a tool definition designed for a range of string values. + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_LONG. */ + protected ToolOutputDefinition buildRangeDefinition(String definitionName, String startValue, String endValue) { + return buildDefinition(definitionName, OutputType.OUTPUT_STRING, startValue, endValue, null); + } + + /** Build a tool definition designed for a single double value, which is likely to be a statistic number of + * questions answered + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_LONG. */ + protected ToolOutputDefinition buildLongOutputDefinition(String definitionName) { + return buildDefinition(definitionName, OutputType.OUTPUT_LONG, null, null, null); + } + + /** Build a tool definition designed for a single double value, which is likely to be a statistic such as average + * number of posts. + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_DOUBLE. */ + protected ToolOutputDefinition buildDoubleOutputDefinition(String definitionName) { + return buildDefinition(definitionName, OutputType.OUTPUT_DOUBLE, null, null, null); + } + + /** Build a tool definition designed for a single boolean value, which is likely to be a test such as + * user has answered all questions correctly. + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_BOOLEAN. */ + protected ToolOutputDefinition buildBooleanOutputDefinition(String definitionName) { + return buildDefinition(definitionName, OutputType.OUTPUT_BOOLEAN, null, null, null); + } + + /** Build a tool definition designed for a single String value. + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_STRING. */ + protected ToolOutputDefinition buildStringOutputDefinition(String definitionName) { + return buildDefinition(definitionName, OutputType.OUTPUT_STRING, null, null, null); + } + + /** Build a tool definition for a complex value output. + * It will get the definition's description from the I18N file using the getDescription() method and + * set the type to OUTPUT_COMPLEX. */ + protected ToolOutputDefinition buildComplexOutputDefinition(String definitionName) { + return buildDefinition(definitionName, OutputType.OUTPUT_COMPLEX, null, null, null); + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/tool/ToolContentManager.java =================================================================== diff -u -r9481bb9c6f8c0e4d6fbed6b230a41c77feda64c6 -r530c208895bfea0ba15e8bd8f1c799b163c059c9 --- lams_common/src/java/org/lamsfoundation/lams/tool/ToolContentManager.java (.../ToolContentManager.java) (revision 9481bb9c6f8c0e4d6fbed6b230a41c77feda64c6) +++ lams_common/src/java/org/lamsfoundation/lams/tool/ToolContentManager.java (.../ToolContentManager.java) (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -23,6 +23,9 @@ /* $$Id$$ */ package org.lamsfoundation.lams.tool; +import java.util.SortedMap; +import java.util.SortedSet; + import org.lamsfoundation.lams.tool.exception.DataMissingException; import org.lamsfoundation.lams.tool.exception.SessionDataExistsException; import org.lamsfoundation.lams.tool.exception.ToolException; @@ -116,5 +119,13 @@ public void importToolContent(Long toolContentId, Integer newUserUid, String toolContentPath, String fromVersion, String toVersion) throws ToolException; - + /** Get the definitions for possible output for an activity, based on the toolContentId. These may be definitions that are always + * available for the tool (e.g. number of marks for Multiple Choice) or a custom definition created for a particular activity + * such as the answer to the third question contains the word Koala and hence the need for the toolContentId + * @return SortedMap of ToolOutputDefinitions with the key being the name of each definition. + * + * Added in LAMS 2.1 + */ + public SortedMap getToolOutputDefinitions(Long toolContentId) throws ToolException; + } Index: lams_common/src/java/org/lamsfoundation/lams/tool/ToolOutputDefinition.java =================================================================== diff -u -r09048f91f2dcbb6b63449f3c1fb9e1a09221a35e -r530c208895bfea0ba15e8bd8f1c799b163c059c9 --- lams_common/src/java/org/lamsfoundation/lams/tool/ToolOutputDefinition.java (.../ToolOutputDefinition.java) (revision 09048f91f2dcbb6b63449f3c1fb9e1a09221a35e) +++ lams_common/src/java/org/lamsfoundation/lams/tool/ToolOutputDefinition.java (.../ToolOutputDefinition.java) (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -23,6 +23,7 @@ /* $Id$ */ package org.lamsfoundation.lams.tool; +import org.apache.commons.lang.builder.CompareToBuilder; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; @@ -49,7 +50,7 @@ * complexDefinition = null; * } */ -public class ToolOutputDefinition { +public class ToolOutputDefinition implements Comparable { private String name; private String description; @@ -129,22 +130,25 @@ ToolOutputDefinition castOther = (ToolOutputDefinition) other; return new EqualsBuilder() .append(this.name, castOther.name) - .append(this.description, castOther.description) .append(this.type, castOther.type) - .append(this.startValue, castOther.startValue) - .append(this.endValue, castOther.endValue) .isEquals(); } public int hashCode() { return new HashCodeBuilder() .append(name) - .append(description) .append(type) - .append(startValue) - .append(endValue) .toHashCode(); } + public int compareTo(Object o) { + + ToolOutputDefinition myClass = (ToolOutputDefinition) o; + return new CompareToBuilder() + .append(this.name, myClass.name) + .append(this.type, myClass.type) + .toComparison(); + } + } Index: lams_common/src/java/org/lamsfoundation/lams/tool/dto/ToolOutputDefinitionDTO.java =================================================================== diff -u -rfb25ea600503d3eea142b305696081f882d6552a -r530c208895bfea0ba15e8bd8f1c799b163c059c9 --- lams_common/src/java/org/lamsfoundation/lams/tool/dto/ToolOutputDefinitionDTO.java (.../ToolOutputDefinitionDTO.java) (revision fb25ea600503d3eea142b305696081f882d6552a) +++ lams_common/src/java/org/lamsfoundation/lams/tool/dto/ToolOutputDefinitionDTO.java (.../ToolOutputDefinitionDTO.java) (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -23,6 +23,7 @@ /* $$Id$$ */ package org.lamsfoundation.lams.tool.dto; +import org.apache.commons.lang.builder.ToStringBuilder; import org.lamsfoundation.lams.tool.ToolOutputDefinition; /** @@ -31,7 +32,7 @@ * This class acts as a data transfer object for * transferring information between FLASH and the core module. * - * This class is required in the authoring enviornment to pass + * This class is required in the authoring environment to pass * information about the ToolOutput for a ToolActivity */ public class ToolOutputDefinitionDTO { @@ -110,4 +111,14 @@ public String getComplexDefinition() { return complexDefinition; } + + public String toString() { + return new ToStringBuilder(this) + .append("name", name) + .append("description", description) + .append("type", type) + .append("startValue", startValue) + .append("endValue", endValue) + .toString(); + } } Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsCoreToolService.java =================================================================== diff -u -r9481bb9c6f8c0e4d6fbed6b230a41c77feda64c6 -r530c208895bfea0ba15e8bd8f1c799b163c059c9 --- lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsCoreToolService.java (.../ILamsCoreToolService.java) (revision 9481bb9c6f8c0e4d6fbed6b230a41c77feda64c6) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsCoreToolService.java (.../ILamsCoreToolService.java) (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -26,10 +26,12 @@ import java.util.List; import java.util.Set; +import java.util.SortedMap; import org.lamsfoundation.lams.learningdesign.Activity; import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.tool.ToolOutputDefinition; import org.lamsfoundation.lams.tool.ToolSession; import org.lamsfoundation.lams.tool.exception.DataMissingException; import org.lamsfoundation.lams.tool.exception.LamsToolServiceException; @@ -167,8 +169,23 @@ * @throws ToolException */ public void notifyToolToDeleteContent(ToolActivity toolActivity) throws ToolException; - + /** + * Ask a tool for its OutputDefinitions, based on the given toolContentId. If the tool doesn't + * have any content matching the toolContentId then it should create the OutputDefinitions based + * on the tool's default content. + * + * This functionality relies on a method added to the Tool Contract in LAMS 2.1, so if the tool + * doesn't support the required method, it writes out an error to the log but doesn't throw + * an exception - just returns an empty map. + * + * @param toolContentId + * @return SortedMap of ToolOutputDefinitions with the key being the name of each definition + * @throws ToolException + */ + public SortedMap getOutputDefinitionsFromTool(Long toolContentId) throws ToolException; + + /** * Update the tool session data. * @param toolSession the new tool session object. */ Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsCoreToolService.java =================================================================== diff -u -r9481bb9c6f8c0e4d6fbed6b230a41c77feda64c6 -r530c208895bfea0ba15e8bd8f1c799b163c059c9 --- lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsCoreToolService.java (.../LamsCoreToolService.java) (revision 9481bb9c6f8c0e4d6fbed6b230a41c77feda64c6) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsCoreToolService.java (.../LamsCoreToolService.java) (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -27,6 +27,8 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import org.apache.log4j.Logger; import org.lamsfoundation.lams.learningdesign.Activity; @@ -37,6 +39,7 @@ import org.lamsfoundation.lams.tool.ToolContent; import org.lamsfoundation.lams.tool.ToolContentIDGenerator; import org.lamsfoundation.lams.tool.ToolContentManager; +import org.lamsfoundation.lams.tool.ToolOutputDefinition; import org.lamsfoundation.lams.tool.ToolSession; import org.lamsfoundation.lams.tool.ToolSessionManager; import org.lamsfoundation.lams.tool.dao.ISystemToolDAO; @@ -318,7 +321,53 @@ throw new ToolException(message,e); } } + /** + * Ask a tool for its OutputDefinitions, based on the given toolContentId. If the tool doesn't + * have any content matching the toolContentId then it should create the OutputDefinitions based + * on the tool's default content. + * + * This functionality relies on a method added to the Tool Contract in LAMS 2.1, so if the tool + * doesn't support the required method, it writes out an error to the log but doesn't throw + * an exception - just returns an empty map. + * + * @param toolContentId + * @return SortedMap of ToolOutputDefinitions with the key being the name of each definition + * @throws ToolException + */ + public SortedMap getOutputDefinitionsFromTool(Long toolContentId) throws ToolException { + + ToolContent toolContent = (ToolContent) toolContentDAO.find(ToolContent.class, toolContentId); + if ( toolContent == null ) { + String error = "The toolContentID "+ toolContentId + " is not valid. No such record exists on the database."; + log.error(error); + throw new DataMissingException(error); + } + + Tool tool = toolContent.getTool(); + if ( tool == null ) { + String error = "The tool for toolContentId "+ toolContentId + " is missing."; + log.error(error); + throw new DataMissingException(error); + } + + try { + ToolContentManager contentManager = (ToolContentManager) findToolService(tool); + return contentManager.getToolOutputDefinitions(toolContentId); + } catch ( NoSuchBeanDefinitionException e ) { + String message = "A tool which is defined in the database appears to missing from the classpath. Unable to copy the tool content. ToolContentId "+toolContentId; + log.error(message,e); + throw new ToolException(message,e); + } catch ( java.lang.AbstractMethodError e ) { + String message = "Tool "+tool.getToolDisplayName()+" doesn't support the getToolOutputDefinitions(toolContentId) method so no output definitions can be accessed."; + log.error(message,e); + throw new ToolException(message,e); + } + + } + + + /** * @see org.lamsfoundation.lams.tool.service.ILamsCoreToolService#updateToolSession(org.lamsfoundation.lams.tool.ToolSession) */ public void updateToolSession(ToolSession toolSession) Index: lams_common/src/java/org/lamsfoundation/lams/util/MessageService.java =================================================================== diff -u -r08950e1090443c3423a3d1c587416a2fccd8bbdf -r530c208895bfea0ba15e8bd8f1c799b163c059c9 --- lams_common/src/java/org/lamsfoundation/lams/util/MessageService.java (.../MessageService.java) (revision 08950e1090443c3423a3d1c587416a2fccd8bbdf) +++ lams_common/src/java/org/lamsfoundation/lams/util/MessageService.java (.../MessageService.java) (revision 530c208895bfea0ba15e8bd8f1c799b163c059c9) @@ -26,35 +26,42 @@ import org.springframework.context.MessageSource; import org.springframework.context.NoSuchMessageException; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.context.support.MessageSourceAccessor; /** * Service class to help Service bean get i18n value quickly. The locale information will get from LAMS_USER * table. For more detail see org.lamsfoundation.lams.web.filter.LocaleFilter. * * @author Steve.Ni * @version $Revision$ - * @see org.springframework.context.support.MessageSourceAccessor + * @see org.springframework.context.support.MessageSource */ public class MessageService { - private MessageSourceAccessor messageAccessor; + private MessageSource messageSource; /** * Set MessageSource from spring IoC. * @param messageSource */ public void setMessageSource(MessageSource messageSource){ - messageAccessor = new MessageSourceAccessor(messageSource); + this.messageSource = messageSource; } /** + * Set MessageSource from spring IoC. + * @param messageSource + */ + public MessageSource getMessageSource(){ + return this.messageSource; + } + + /** * @see org.springframework.context.support.MessageSourceAccessor#getMessage(java.lang.String) * @param key * @return */ public String getMessage(String key){ String message; try { - message = messageAccessor.getMessage(key,LocaleContextHolder.getLocale()); + message = messageSource.getMessage(key,null,LocaleContextHolder.getLocale()); } catch (NoSuchMessageException e) { message = "??" + key + "??"; } @@ -69,7 +76,7 @@ public String getMessage(String key, String defaultMessage){ String message = defaultMessage; try { - message = messageAccessor.getMessage(key,defaultMessage,LocaleContextHolder.getLocale()); + message = messageSource.getMessage(key,null,defaultMessage,LocaleContextHolder.getLocale()); } catch (NoSuchMessageException e) { message = defaultMessage; } @@ -84,7 +91,7 @@ public String getMessage(String key, Object[] args){ String message; try { - message = messageAccessor.getMessage(key,args,LocaleContextHolder.getLocale()); + message = messageSource.getMessage(key,args,LocaleContextHolder.getLocale()); } catch (NoSuchMessageException e) { message = "??" + key + "??"; } @@ -100,7 +107,7 @@ public String getMessage(String key, Object[] args, String defaultMessage){ String message = defaultMessage; try { - message = messageAccessor.getMessage(key,args,defaultMessage,LocaleContextHolder.getLocale()); + message = messageSource.getMessage(key,args,defaultMessage,LocaleContextHolder.getLocale()); } catch (NoSuchMessageException e) { message = defaultMessage; }