Index: lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandler.java =================================================================== RCS file: /usr/local/cvsroot/lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandler.java,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lams_contentrepository/src/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandler.java 20 Jul 2005 05:39:47 -0000 1.1 @@ -0,0 +1,306 @@ +/* +Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +http://www.gnu.org/licenses/gpl.txt +*/ +package org.lamsfoundation.lams.contentrepository.client; + +import java.io.InputStream; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.contentrepository.AccessDeniedException; +import org.lamsfoundation.lams.contentrepository.FileException; +import org.lamsfoundation.lams.contentrepository.ICredentials; +import org.lamsfoundation.lams.contentrepository.ITicket; +import org.lamsfoundation.lams.contentrepository.IValue; +import org.lamsfoundation.lams.contentrepository.IVersionedNode; +import org.lamsfoundation.lams.contentrepository.InvalidParameterException; +import org.lamsfoundation.lams.contentrepository.ItemExistsException; +import org.lamsfoundation.lams.contentrepository.ItemNotFoundException; +import org.lamsfoundation.lams.contentrepository.NodeKey; +import org.lamsfoundation.lams.contentrepository.PropertyType; +import org.lamsfoundation.lams.contentrepository.RepositoryCheckedException; +import org.lamsfoundation.lams.contentrepository.ValueFormatException; +import org.lamsfoundation.lams.contentrepository.WorkspaceNotFoundException; +import org.lamsfoundation.lams.contentrepository.service.IRepositoryService; +import org.lamsfoundation.lams.contentrepository.service.SimpleCredentials; + +/** + * Handles a simple connection to the content repository, and allows a file to be stored and + * retrieved. It can be used by LAMS tools to do the handling the files for + * offline and online instructions. It is a very simple client, in that + * it only handles files (and not packages) and will not keep versions + * of files - to do a new version of a file, the old version is deleted + * and the new version added. + * + * To use this class: + * + * For example: + *
+ * 	
+ * 		
+ *	  
+ * 
+ * + * @author conradb, Fiona Malikoff + */ +public abstract class ToolContentHandler { + + /** File is for Online Instructions */ + public final static String TYPE_ONLINE = "ONLINE"; + /** File is for Offline Instructions */ + public final static String TYPE_OFFLINE = "OFFLINE"; + + /** The "name" used to store the online/offline property in the repository */ + public final static String FILE_TYPE_PROPERTY_NAME = "TYPE"; + + private IRepositoryService repositoryService; + private ITicket ticket; + private boolean productionMode = true; + + protected Logger log = Logger.getLogger(ToolContentHandler.class.getName()); + + /** + * @return Returns the repositoryWorkspaceName. + */ + public abstract String getRepositoryWorkspaceName() ; + + /** + * @return Returns the repositoryUser. + */ + public abstract String getRepositoryUser(); + + /** + * @return Returns the repository identification string. This is the + * "password" field the credential. + */ + public abstract char[] getRepositoryId(); + + private void configureContentRepository() throws RepositoryCheckedException { + ICredentials cred = new SimpleCredentials(getRepositoryUser(), getRepositoryId()); + try { + getRepositoryService().createCredentials(cred); + getRepositoryService().addWorkspace(cred,getRepositoryWorkspaceName()); + } catch (ItemExistsException ie) { + log.warn("Tried to configure repository but it " + +" appears to be already configured." + +"Workspace name "+getRepositoryWorkspaceName() + +". Exception thrown by repository being ignored. ", ie); + } catch (RepositoryCheckedException e) { + log.error("Error occured while trying to configure repository." + +"Workspace name "+getRepositoryWorkspaceName() + +" Unable to recover from error: "+e.getMessage(), e); + throw e; + } + } + + /** + * Get the ticket to access the repository. If the workspace/credential + * hasn't been set up, then it will be set up automatically. + * + * @forceLogin set to true if tried to do something and got access denied. This may happen + * if the repository loses the ticket. + * @return the repository ticket + */ + protected ITicket getTicket( boolean forceLogin ) throws RepositoryCheckedException { + if ( ticket == null || forceLogin ) { + ICredentials cred = new SimpleCredentials(getRepositoryUser(), getRepositoryId()); + try { + try { + ticket = getRepositoryService().login(cred, getRepositoryWorkspaceName()); + } catch ( WorkspaceNotFoundException e ) { + log.error("Content Repository workspace "+getRepositoryWorkspaceName() + +" not configured. Attempting to configure now."); + configureContentRepository(); + ticket = getRepositoryService().login(cred, getRepositoryWorkspaceName()); + } + } catch ( RepositoryCheckedException e ) { + log.error("Unable to get ticket for workspace "+getRepositoryWorkspaceName(),e); + throw e; + } + } + return ticket; + } + + /** + * Save a file in the content repository. + * + * @param stream Input filestream. Mandatory. + * @param fileName Input filename. Mandatory. + * @param mimeType Mimetype of file. Optional. + * @param fileProperty is this for online or offline instructions? Should be TYPE_ONLINE or TYPE_OFFLINE. Mandatory. + * @return key to the new content repository node + * @throws InvalidParameterException One of the mandatory parameters is missing. + * @throws FileException An error occured writing the input stream to disk. + * @throws RepositoryCheckedException Some other error occured. + */ + public NodeKey uploadFile(InputStream stream, String fileName, String mimeType, String fileProperty) throws RepositoryCheckedException, InvalidParameterException, RepositoryCheckedException { + if ( fileProperty == null ) + throw new InvalidParameterException("uploadFile: fileProperty parameter empty. Should be either TYPE_ONLINE or TYPE_OFFLINE"); + + NodeKey nodeKey = null; + try { + try { + nodeKey = getRepositoryService().addFileItem(getTicket(false), stream, fileName, mimeType, null); + } catch (AccessDeniedException e) { + log.warn("Unable to access repository to add file "+fileName + +"AccessDeniedException: "+e.getMessage()+" Retrying login."); + nodeKey = getRepositoryService().addFileItem(getTicket(true), stream, fileName, mimeType, null); + } + + try { + getRepositoryService().setProperty(getTicket(false), nodeKey.getUuid(), nodeKey.getVersion(), FILE_TYPE_PROPERTY_NAME, fileProperty, PropertyType.STRING); + } catch (AccessDeniedException e) { + log.warn("Unable to access repository to set offline/online parameter "+fileName + +"AccessDeniedException: "+e.getMessage()+" Retrying login."); + getRepositoryService().setProperty(getTicket(true), nodeKey.getUuid(), nodeKey.getVersion(), FILE_TYPE_PROPERTY_NAME, fileProperty, PropertyType.STRING); + } + + } catch (RepositoryCheckedException e2) { + log.warn("Unable to to uploadFile"+fileName + +"Repository Exception: "+e2.getMessage()+" Retry not possible."); + throw e2; + } + + return nodeKey; + } + + /** + * Delete a file node. If the node does not exist, then nothing happens (ie ItemNotFoundException is NOT thrown). + * @param uuid id of the file node. Mandatory + * @throws InvalidParameterException One of the mandatory parameters is missing. + * @throws RepositoryCheckedException Some other error occured. + */ + public void deleteFile(Long uuid) throws InvalidParameterException, RepositoryCheckedException { + try { + try { + getRepositoryService().deleteNode(getTicket(false), uuid); + } catch (AccessDeniedException e) { + log.warn("Unable to access repository to delete file id"+uuid + +"AccessDeniedException: "+e.getMessage()+" Retrying login."); + getRepositoryService().deleteNode(getTicket(true), uuid); + } + } catch (ItemNotFoundException e1) { + // didn't exist so don't need to delete. Ignore problem. + } catch (RepositoryCheckedException e2) { + log.error("Unable delete file id"+uuid + +"Repository Exception: "+e2.getMessage()+" Retry not possible."); + throw e2; + } + } + + /** Get a file node. + * @param uuid id of the file node. Mandatory + * @throws FileException An error occured writing the input stream to disk. + * @throws ItemNotFoundException This file node does not exist, so cannot delete it. + * @throws RepositoryCheckedException Some other error occured. + */ + public IVersionedNode getFileNode(Long uuid) throws ItemNotFoundException, FileException, RepositoryCheckedException { + try { + try { + return getRepositoryService().getFileItem(getTicket(false), uuid, null); + } catch (AccessDeniedException e) { + log.warn("Unable to access repository to get file id"+uuid + +"AccessDeniedException: "+e.getMessage()+" Retrying login."); + return getRepositoryService().getFileItem(getTicket(true), uuid, null); + } + } catch (RepositoryCheckedException e2) { + log.warn("Unable to to get file id"+uuid + +"Repository Exception: "+e2.getMessage()+" Retry not possible."); + throw e2; + } +} + + public boolean isOffline(IVersionedNode node) { + return checkType(node, TYPE_OFFLINE); + } + + public boolean isOnline(IVersionedNode node) { + return checkType(node, TYPE_ONLINE); + } + + /** Check the FILE_TYPE_PROPERTY_NAME property against the expectedType. + * + * @param node should not be null + * @param expectedType must not be null + * @return true if match, false otherwise. + */ + protected boolean checkType(IVersionedNode node, String expectedType) { + IValue property = node.getProperty(FILE_TYPE_PROPERTY_NAME); + String value = null; + try { + value = property != null ? property.getString() : null; + } catch (ValueFormatException e) { + // hmm, not expecting this - so need to return false... + // leave value as null; + } + return ( node != null && expectedType!= null && expectedType.equals(value)); + } + + + +/* protected File getFile(String fileName, InputStream is) throws FileNotFoundException, Exception { + InputStream in = new BufferedInputStream(is, 500); + File file = new File(fileName); + OutputStream out = new BufferedOutputStream(new FileOutputStream(file), 500); + int bytes; + while ((bytes = in.available()) > 0) { + byte[] byteArray = new byte[bytes]; + in.read(byteArray); + out.write(byteArray); + } + in.close(); + out.close(); + out.flush(); + return file; + } + + protected byte[] getBytes(File file) throws FileNotFoundException, Exception { + byte[] byteArray = new byte[(int) file.length()]; + FileInputStream stream = new FileInputStream(file); + stream.read(byteArray); + stream.close(); + return byteArray; + } */ + + /* *** Required for Spring bean creation **************************/ + + /** + * @return Returns the repositoryService. + */ + public IRepositoryService getRepositoryService() { + return repositoryService; + } + /** + * @param repositoryService The repositoryService to set. + */ + public void setRepositoryService(IRepositoryService repositoryService) { + this.repositoryService = repositoryService; + } +} Index: lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/testApplicationContext.xml =================================================================== RCS file: /usr/local/cvsroot/lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/Attic/testApplicationContext.xml,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/testApplicationContext.xml 20 Jul 2005 05:39:47 -0000 1.1 @@ -0,0 +1,9 @@ + + + + + + + + + Index: lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/client/TestToolContentHandlerImpl.java =================================================================== RCS file: /usr/local/cvsroot/lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/client/Attic/TestToolContentHandlerImpl.java,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/client/TestToolContentHandlerImpl.java 20 Jul 2005 05:39:47 -0000 1.1 @@ -0,0 +1,141 @@ +/* +Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +http://www.gnu.org/licenses/gpl.txt +*/ +package org.lamsfoundation.lams.contentrepository.client; + +import org.lamsfoundation.lams.contentrepository.ITicket; +import org.lamsfoundation.lams.contentrepository.IVersionedNode; +import org.lamsfoundation.lams.contentrepository.ItemNotFoundException; +import org.lamsfoundation.lams.contentrepository.NodeKey; +import org.lamsfoundation.lams.contentrepository.data.CRResources; +import org.lamsfoundation.lams.contentrepository.service.BaseTestCase; + +/** + * @author Fiona Malikoff + * + * Indirect testing of ToolContentHandler class. Have done + * an implementation (ToolContentHandlerImpl) and will test + * the implemented class (rather hard to test an abstract class!) + */ +public class TestToolContentHandlerImpl extends BaseTestCase { + + public static final String HANDLER_BEAN="toolContentHandler"; + ToolContentHandlerImpl handler = null; + + /** + * Constructor for ToolContentHandlerImplTest. + */ + public TestToolContentHandlerImpl() { + super(); + } + + public void setUp() throws Exception { + super.setUp(); + handler = (ToolContentHandlerImpl) context.getBean(HANDLER_BEAN); + } + + public void tearDown() throws Exception { + super.tearDown(); + handler = null; + } + + public void testGetRepositoryWorkspaceName() { + assertEquals(handler.repositoryWorkspace,handler.getRepositoryWorkspaceName()); + } + + public void testGetRepositoryUser() { + assertEquals(handler.repositoryUser,handler.getRepositoryUser()); + } + + public void testGetRepositoryId() { + assertEquals(handler.repositoryId,handler.getRepositoryId()); + } + + public void testGetRepositoryService() { + try { + assertNotNull(handler.getRepositoryService()); + } catch ( Exception e ) { + e.printStackTrace(); + fail("getRepService() threw exception "+e.getMessage()); + } + } + + public void testGetTicket() { + try { + ITicket ticket1 = handler.getTicket(false); + // repeat call using false should get same ticket; + ITicket ticket2 = handler.getTicket(false); + assertSame(ticket1, ticket2); + // repeat call using true should get new ticket; + ITicket ticket3 = handler.getTicket(true); + assertNotSame(ticket1, ticket3); + } catch ( Exception e ) { + e.printStackTrace(); + fail("getRepositoryId() threw exception "+e.getMessage()); + } + } + + /* Creates an offline and online file, checks that the properties are correct and then deletes them. */ + public void testUploadFile() { + try { + NodeKey offlineNodeKey = handler.uploadFile(CRResources.getSingleFile(), CRResources.singleFileName, + CRResources.singleFileMimeType, ToolContentHandlerImpl.TYPE_OFFLINE); + NodeKey onlineNodeKey = handler.uploadFile(CRResources.getSingleFile(), CRResources.singleFileName, + CRResources.singleFileMimeType, ToolContentHandlerImpl.TYPE_ONLINE); + + Long uuid = offlineNodeKey.getUuid(); + IVersionedNode node = handler.getFileNode(uuid); + assertNotNull("Offline node can be retrieved", node); + assertNotNull("Offline file can be retrieved", node.getFile()); + assertTrue("Offline file is marked as offline", handler.isOffline(node)); + assertFalse("Offline file is not marked as online ", handler.isOnline(node)); + handler.deleteFile(uuid); + try { + node = handler.getFileNode(uuid); + fail("Expected ItemNotFoundException to be thrown when trying to access deleted offline file."); + } catch (ItemNotFoundException ie) { + assertTrue("Offline node cannot be retrieved after deletion",true); + } + + uuid = onlineNodeKey.getUuid(); + node = handler.getFileNode(uuid); + assertNotNull("Online node can be retrieved", node); + assertNotNull("Online file can be retrieved", node.getFile()); + assertFalse("Online file is not marked as offline", handler.isOffline(node)); + assertTrue("Online file is marked as online ", handler.isOnline(node)); + handler.deleteFile(uuid); + try { + node = handler.getFileNode(uuid); + fail("Expected ItemNotFoundException to be thrown when trying to access deleted online file."); + } catch (ItemNotFoundException ie) { + assertTrue("Online node cannot be retrieved after deletion", true); + } + + // check deleting a missing node doesn't cause a problem + handler.deleteFile(uuid); + + } catch ( Exception e ) { + e.printStackTrace(); + fail("testUploadFile threw exception "+e.getMessage()); + } + } + + +} Index: lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandlerImpl.java =================================================================== RCS file: /usr/local/cvsroot/lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/client/Attic/ToolContentHandlerImpl.java,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/client/ToolContentHandlerImpl.java 20 Jul 2005 05:39:47 -0000 1.1 @@ -0,0 +1,61 @@ +/* +Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +http://www.gnu.org/licenses/gpl.txt +*/ +package org.lamsfoundation.lams.contentrepository.client; + +import org.lamsfoundation.lams.contentrepository.RepositoryCheckedException; + +/** + * + * @author Fiona Malikoff + * + * This is a test implementation of the ContentHandler. Each tool would + * derive its own class similar to this. + */ +public class ToolContentHandlerImpl extends ToolContentHandler { + + protected String repositoryUser = "testcontenthandleruser"; + protected char[] repositoryId = {'t','e','s','t','i','d'}; + protected String repositoryWorkspace = "testcontenthandlerworkspace"; + + /* (non-Javadoc) + * @see org.lamsfoundation.lams.contentrepository.client.ContentHandler#getRepositoryWorkspaceName() + */ + public String getRepositoryWorkspaceName() { + return repositoryWorkspace; + } + + /* (non-Javadoc) + * @see org.lamsfoundation.lams.contentrepository.client.ContentHandler#getRepositoryUser() + */ + public String getRepositoryUser() { + return repositoryUser; + } + + /* (non-Javadoc) + * @see org.lamsfoundation.lams.contentrepository.client.ContentHandler#getRepositoryId() + */ + public char[] getRepositoryId() { + return repositoryId; + } + + + +} Index: lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/data/CRResources.java =================================================================== RCS file: /usr/local/cvsroot/lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/data/Attic/CRResources.java,v diff -u -r1.1 -r1.2 --- lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/data/CRResources.java 9 May 2005 10:32:37 -0000 1.1 +++ lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/data/CRResources.java 20 Jul 2005 05:39:47 -0000 1.2 @@ -36,6 +36,7 @@ public class CRResources { public static final String singleFileName = "TextFile.txt"; + public static final String singleFileMimeType = "text/plain"; public static final String zipFileName = "bopcp.zip"; public static final String zipFileIncludesFilename = "index.html"; Index: lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/service/BaseTestCase.java =================================================================== RCS file: /usr/local/cvsroot/lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/service/Attic/BaseTestCase.java,v diff -u -r1.2 -r1.3 --- lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/service/BaseTestCase.java 9 May 2005 10:32:37 -0000 1.2 +++ lams_contentrepository/test/java/org/lamsfoundation/lams/contentrepository/service/BaseTestCase.java 20 Jul 2005 05:39:47 -0000 1.3 @@ -56,7 +56,10 @@ // this is run for each test so once we have it, we don't // want to get it again! if ( context == null ) { - context = new ClassPathXmlApplicationContext(IRepositoryService.LOCAL_CONTEXT_PATH); + context = new ClassPathXmlApplicationContext(new String[] { + IRepositoryService.LOCAL_CONTEXT_PATH, + "/org/lamsfoundation/lams/contentrepository/testApplicationContext.xml" + }); } if ( repository == null ) {