Index: content/content-api/api/src/java/org/sakaiproject/content/api/ResourceContentHandler.java
===================================================================
--- content/content-api/api/src/java/org/sakaiproject/content/api/ResourceContentHandler.java	(revision 0)
+++ content/content-api/api/src/java/org/sakaiproject/content/api/ResourceContentHandler.java	(revision 0)
@@ -0,0 +1,10 @@
+package org.sakaiproject.content.api;
+
+import java.io.InputStream;
+
+public interface ResourceContentHandler {
+
+	public byte[] getResourceBody();
+
+	public InputStream streamResourceBody();
+}
Index: content/content-api/api/src/java/org/sakaiproject/content/api/ContentResourceHandler.java
===================================================================
--- content/content-api/api/src/java/org/sakaiproject/content/api/ContentResourceHandler.java	(revision 0)
+++ content/content-api/api/src/java/org/sakaiproject/content/api/ContentResourceHandler.java	(revision 0)
@@ -0,0 +1,63 @@
+package org.sakaiproject.content.api;
+
+import org.sakaiproject.entity.api.Entity;
+
+public interface ContentResourceHandler {
+	/*
+	 * The content path for the access servlet
+	 */
+	public static final String CONTENT_REFERENCE_ROOT = Entity.SEPARATOR + "content";
+	/*
+	 * MIME type for ims content packaging resource, this is defined in the specification
+	 */
+	public static final String IMS_CP_MIMETYPE = "ims/cp";
+	/*
+	 * The flag used to determine whether the requested resource id is a content pack or not.
+	 * This can also be determined from the MIME type of the resource
+	 */
+	public static final String IMS_CP_FLAG = ".ims";
+	/*
+	 * manifest file name specified in the ims content packaging specification, this is the
+	 * only name accepted
+	 */
+	public static final String IMS_CP_DEFAULTMANIFEST = "/imsmanifest.xml";
+
+	/*
+	 * Handles the resource returned by basic ContenHostingService, if resource is NOT null. Otherwise 
+	 * processes the id and returns appropriate resource for requested conteng type. e.g. resource
+	 * get from a zip file
+	 * @param id the id of the resource to be handled
+	 * @param resource The default resource returned by ContentHostingService for id, may be
+	 * NULL
+	 * @return The resource corresponding for id or NULL when resource doesn't exit or isn't
+	 * supported by this Handler
+	 * */
+	public ContentResource verifyContent(String id, ContentResource resource);
+	
+
+	/*
+	 * Init method for this ContentResourceHandler. Generally, the handler registers itself with the
+	 * ContentHostingService.
+	 */
+	public void init();
+	
+	/*
+	 * destroy method for this ContentResourceHanlder.
+	 * The typical work is to deregister itself from the ContentHostingService's content hanlder
+	 * list
+	 */
+	public void destroy();
+	
+	/*
+	 * Getter method for the ContentHostingService
+	 * @return ContentHostingService this handler registered with
+	 */
+	public ContentHostingService getService();
+	
+	/*
+	 * Setter method for the ContentHostingService. The handler holds a reference to 
+	 * the ContentHostingService
+	 * @param service ContentHostingService this handler will register with
+	 */
+	public void setService(ContentHostingService service);
+}
Index: content/content-api/api/src/java/org/sakaiproject/content/api/ContentHostingService.java
===================================================================
--- content/content-api/api/src/java/org/sakaiproject/content/api/ContentHostingService.java	(revision 9057)
+++ content/content-api/api/src/java/org/sakaiproject/content/api/ContentHostingService.java	(working copy)
@@ -1212,5 +1212,29 @@
 	 *        The id for the collection.
 	 */
 	public Collection getGroupsWithReadAccess(String collectionId);
+	
+	// Xuan Added Method
+	/*
+	 * Registers a ContentHandler in this content service
+	 * @param ch ContentResourceHandler to be registerd into this ContentHostingService
+	 */
+	public void registerContentHandler(ContentResourceHandler ch);
+	
+	/*
+	 * deregisters the specified ContentResourceHandler ch from this content service's special
+	 * content handler list
+	 * @param ch the ContentResourceHandler to be deregistered
+	*/
+	public void deRegisterCh(ContentResourceHandler ch);
+	
+	/*
+	 * Factory method for creating a simulate ContentResourceEdit. A ResouceContentHandler
+	 * can be specified for the ContentResource
+	 * @param id identifier for the created ContentResource
+	 * @param rch ResourceContentHanlder for the result ContentResource
+	 */
+	public ContentResourceEdit newContentResource(String id, ResourceContentHandler rch);
 
+	// End. Xuan added
+
 }
Index: content/content-api/api/src/java/org/sakaiproject/content/api/ContentResourceEdit.java
===================================================================
--- content/content-api/api/src/java/org/sakaiproject/content/api/ContentResourceEdit.java	(revision 9057)
+++ content/content-api/api/src/java/org/sakaiproject/content/api/ContentResourceEdit.java	(working copy)
@@ -62,6 +62,13 @@
 	 * @param time The date/time at which access to the entity should be restricted.
 	 */
 	public void getRetractDate(Time time);
+	
+	/**
+	 * Set the content handler for this resource
+	 * @param handler A ResourceContentHandler for this resource, if null means no specific 
+	 * content handler for this resource
+	 */
+	public void setContentHandler(ResourceContentHandler handler);
 
 }	// ContentResourceEdit
 
Index: content/content-impl/impl/src/java/org/sakaiproject/content/impl/BaseContentService.java
===================================================================
--- content/content-impl/impl/src/java/org/sakaiproject/content/impl/BaseContentService.java	(revision 9057)
+++ content/content-impl/impl/src/java/org/sakaiproject/content/impl/BaseContentService.java	(working copy)
@@ -63,6 +63,8 @@
 import org.sakaiproject.content.api.ContentHostingService;
 import org.sakaiproject.content.api.ContentResource;
 import org.sakaiproject.content.api.ContentResourceEdit;
+import org.sakaiproject.content.api.ContentResourceHandler;
+import org.sakaiproject.content.api.ResourceContentHandler;
 import org.sakaiproject.content.api.GroupAwareEntity;
 import org.sakaiproject.content.cover.ContentTypeImageService;
 import org.sakaiproject.entity.api.ContextObserver;
@@ -102,6 +104,7 @@
 import org.sakaiproject.site.api.Group;
 import org.sakaiproject.site.api.Site;
 import org.sakaiproject.site.api.SiteService;
+import org.sakaiproject.thread_local.cover.ThreadLocalManager;
 import org.sakaiproject.time.api.Time;
 import org.sakaiproject.time.cover.TimeService;
 import org.sakaiproject.tool.api.SessionBindingEvent;
@@ -140,7 +143,50 @@
 
 	/** Maximum number of characters in a valid resource-id */
 	protected static final int MAXIMUM_RESOURCE_ID_LENGTH = 255;
+	
+	/* Added member by Xuan*/
+	protected List specialContentHandler = null;
 
+/*
+ * registers the specified ContentResourceHanlder ch as one special content handler in this
+ * content service
+ * @param ch The ContentResourceHandler to be registered
+ */
+	public void registerContentHandler(ContentResourceHandler ch) {
+		if (specialContentHandler == null) {
+			specialContentHandler = new ArrayList();
+		}
+		if (ch != null)
+			specialContentHandler.add(ch);
+	}
+
+/*
+ * deregisters the specified ContentResourceHandler ch from this content service's special
+ * content handler list
+ * @param ch the ContentResourceHandler to be deregistered
+*/
+	public void deRegisterCh(ContentResourceHandler ch) {
+		if (ch == null)
+			return;
+		if(specialContentHandler != null){
+			specialContentHandler.remove(ch);
+		}
+	}
+
+/*
+ * creates a phantom ContentResouce without real content stored in the backend data storage
+ * with given id and ResourceContnentHandler rch.
+ * @param id The id of Content Resource being created
+ * @param rch specific content handler for this new created ContentResource
+*/
+	public ContentResourceEdit newContentResource(String id, ResourceContentHandler rch) {
+		ContentResourceEdit cre = new BaseResourceEdit(id);
+		cre.setContentHandler(rch);
+		return cre;
+	}
+	
+	/* END - Added member by Xuan */
+
 	/** The initial portion of a relative access point URL. */
 	protected String m_relativeAccessPoint = null;
 
@@ -371,6 +417,7 @@
 	{
 		try
 		{
+			specialContentHandler = new ArrayList();
 			m_relativeAccessPoint = REFERENCE_ROOT;
 
 			// construct a storage helper and read
@@ -2314,6 +2361,21 @@
 			}
 		}
 
+		// Process the find request through the ContentResource handler this conent hosting service
+		// has first
+		// Xuan Added
+		if(specialContentHandler == null)
+			System.out.println("God, it's null");
+		for ( Iterator i = specialContentHandler.iterator(); i.hasNext(); ) {
+			ContentResourceHandler crh = (ContentResourceHandler) i.next();
+			resource = crh.verifyContent(id, resource);
+			if(resource != null){
+				break;
+			}
+		}
+
+		//:~) Xuan Added End
+		
 		return resource;
 
 	} // findResource
@@ -4236,6 +4298,12 @@
 					Collection copyrightAcceptedRefs) throws EntityPermissionException, EntityNotDefinedException,
 					EntityAccessOverloadException, EntityCopyrightException
 			{
+				// 
+				// Added by Xuan
+				ThreadLocalManager.set("contentplugin.request", req);
+				ThreadLocalManager.set("contentplugin.response", res);
+				// End Added Xuan
+				
 				// if the id is null, the request was for just ".../content"
 				String refId = ref.getId();
 				if (refId == null) refId = "";
@@ -4246,6 +4314,11 @@
 					handleAccessResource(req, res, ref, copyrightAcceptedRefs);
 					return;
 				}
+				if (refId.indexOf(ContentResourceHandler.IMS_CP_FLAG) != -1)
+				{
+					handleAccessResource(req, res, ref, copyrightAcceptedRefs);
+					return;
+				}
 
 				// test for a collection
 				if (m_storage.checkCollection(refId))
@@ -7112,6 +7185,8 @@
 
 		/** The date/time before which the entity should not be generally available */
 		protected Time m_releaseDate = TimeService.newTime(0);
+		
+		protected ResourceContentHandler m_resourceContentHandler = null;
 
 		/**
 		 * Construct.
@@ -7416,9 +7491,14 @@
 
 			if ((rv == null) && (m_contentLength > 0))
 			{
-				// TODO: we do not store the body with the object, so as not to cache the body bytes -ggolden
-				rv = m_storage.getResourceBody(this);
-				// m_body = rv;
+				if(m_resourceContentHandler != null){
+					return m_resourceContentHandler.getResourceBody();
+				}else{
+					// TODO: we do not store the body with the object, so as not to cache the body bytes -ggolden
+					rv = m_storage.getResourceBody(this);
+					// m_body = rv;
+				}
+
 			}
 
 			return rv;
@@ -7434,12 +7514,11 @@
 		{
 			InputStream rv = null;
 
-			if (m_body != null)
-			{
+			if (m_body != null)	{
 				rv = new ByteArrayInputStream(m_body);
-			}
-			else
-			{
+			}	else if(m_resourceContentHandler != null){
+				rv = m_resourceContentHandler.streamResourceBody();
+			} else {
 				rv = m_storage.streamResourceBody(this);
 			}
 
@@ -7698,6 +7777,18 @@
 			return m_retractDate;
 		}
 		
+		// Xuan added
+		public ResourceContentHandler getContentHandler() {
+			return m_resourceContentHandler;
+		}
+
+		public void setContentHandler(ResourceContentHandler resourceContentHandler) {
+			m_resourceContentHandler = resourceContentHandler;
+			if(m_resourceContentHandler != null)
+				m_body = m_resourceContentHandler.getResourceBody();
+		}
+		// Xuan Added End
+		
 	} // BaseResource
 
 	/**********************************************************************************************************************************************************************************************************************************************************
