/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/content/impl/serialize/impl/Type1BaseContentCollectionSerializer.java $
* $Id: Type1BaseContentCollectionSerializer.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.content.impl.serialize.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import org.sakaiproject.content.api.ResourceType;
import org.sakaiproject.content.api.GroupAwareEntity.AccessMode;
import org.sakaiproject.content.impl.serialize.api.SerializableCollectionAccess;
import org.sakaiproject.entity.api.serialize.EntityParseException;
import org.sakaiproject.entity.api.serialize.EntitySerializer;
import org.sakaiproject.entity.api.serialize.SerializableEntity;
import org.sakaiproject.time.api.Time;
import org.sakaiproject.time.api.TimeService;
import org.sakaiproject.util.ByteStorageConversion;
import org.sakaiproject.util.serialize.Type1BaseResourcePropertiesSerializer;
/**
* <pre>
* Serializes ContentCollections using a Type1 block serializer, outputs a string with
* char in the byte range 0-255 that can be saved in a CBLOB or BLOB or on file.
* These MUST be saved as UTF8.
* If you need to modify this class think very carefully about what data might be in production databases.
* DO NOT add new fields to existing blocks
* DO NOT remove fields from blocks.
* If you need more structure add new unique blocks.
* If you need to change the structure, create a Type2 class and change the Type number so that type 1
* serializations continue to work.
*
* The general structure of a serialization is
* char 1-6 : BLOB_ID identifying the blob.
* First Int: Type Number (int)
* Next Int: Block number identifying block
* The contents and length of the block is determined by the serailizer writing the block
* When the serializer has finished writing the block the next int should be the next block number.
*
* BLOB_ID's MUST be unique over all BLOBS.
* TYPE Numbers MUST be unique within BLOBS of the same type
* BLOCK Numbers MUST be unique within Types, but SHOULD be unique within BLOBS of the same type.
* For variable lenght data, always serialize the length before the data. (eg number of elements, size of byte[])
* For UTF Strings that are likely to be over 64K, you MUST serialize as lenght:UTF8byte[] as writeUTF will only
* handle 64K
*
* This structure is shared with the Type1ResoruceSerializer
* BLOCK1
* General Attributes
* BLOCK2
* release and retract dates
* BLOCK3 (varialble)
* Groups
* BLOCK4
* Properties (managed by a seperate serializer
* BLOCK_END
* </pre>
*
* @author ieb
*/
public class Type1BaseContentCollectionSerializer implements EntitySerializer
{
public static final String BLOB_ID = "CHSBCE";
private static final byte[] BYTE_BLOB_ID = new byte[] { 'C', 'H', 'S', 'B', 'C', 'E' };
// These are block markers in the serialization, DO NOT reuse,
// create a new type or new block.
// If you re-use you will have to provide data migration
public static final int TYPE1 = 1;
public static final int BLOCK1 = 10;
public static final int BLOCK2 = 11;
public static final int BLOCK3 = 12;
public static final int BLOCK4 = 13;
public static final int BLOCK_END = 2;
private Type1BaseResourcePropertiesSerializer baseResourcePropertiesSerializer = new Type1BaseResourcePropertiesSerializer();
private TimeService timeService;
/**
* @deprecated
* @param se
* @param serialized
* @throws EntityParseException
*/
private void parseString(SerializableEntity se, String serialized)
throws EntityParseException
{
if (!(se instanceof SerializableCollectionAccess))
{
throw new EntityParseException("Cant serialize " + se
+ " as it is not a SerializableCollection ");
}
SerializableCollectionAccess sc = (SerializableCollectionAccess) se;
try
{
if (!serialized.startsWith(BLOB_ID))
{
throw new EntityParseException(
"Data Block does not belong to this serializer got ["
+ serialized.substring(0, BLOB_ID.length())
+ "] expected [" + BLOB_ID + "]");
}
char[] cbuf = serialized.toCharArray();
byte[] sb = new byte[cbuf.length - BLOB_ID.length()];
ByteStorageConversion.toByte(cbuf, BLOB_ID.length(), sb, 0, sb.length);
ByteArrayInputStream baos = new ByteArrayInputStream(sb);
DataInputStream ds = new DataInputStream(baos);
doParse(sc, ds);
}
catch (EntityParseException epe)
{
throw epe;
}
catch (Exception ex)
{
throw new EntityParseException("Failed to parse entity", ex);
}
}
public void parse(SerializableEntity se, byte[] buffer)
throws EntityParseException
{
if (!(se instanceof SerializableCollectionAccess))
{
throw new EntityParseException("Cant serialize " + se
+ " as it is not a SerializableCollection ");
}
SerializableCollectionAccess sc = (SerializableCollectionAccess) se;
for (int i = 0; i < BYTE_BLOB_ID.length; i++)
{
if (buffer[i] != BYTE_BLOB_ID[i])
{
throw new EntityParseException(
"Data Block does not belong to this serializer got ["
+ new String(buffer,0,BYTE_BLOB_ID.length) + "] expected [" + new String(BYTE_BLOB_ID) + "]");
}
}
try
{
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
DataInputStream ds = new DataInputStream(bais);
byte[] signature = new byte[BYTE_BLOB_ID.length];
ds.read(signature);
doParse(sc, ds);
}
catch (EntityParseException epe)
{
throw epe;
}
catch (Exception ex)
{
throw new EntityParseException("Failed to parse entity", ex);
}
}
/**
* @deprecated
* @param se
* @return
* @throws EntityParseException
*/
private String serializeString(SerializableEntity se) throws EntityParseException
{
if (!(se instanceof SerializableCollectionAccess))
{
throw new EntityParseException("Cant serialize " + se
+ " as it is not a SerializableCollectionAccess ");
}
SerializableCollectionAccess sc = (SerializableCollectionAccess) se;
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream ds = new DataOutputStream(baos);
doSerialize(sc, ds);
ds.flush();
baos.flush();
byte[] op = baos.toByteArray();
char[] opc = new char[op.length + BLOB_ID.length()];
int bid = BLOB_ID.length();
ByteStorageConversion.toChar(op, 0, opc, bid, op.length);
for (int i = 0; i < bid; i++)
{
opc[i] = BLOB_ID.charAt(i);
}
return new String(opc);
}
catch (Exception ex)
{
throw new EntityParseException("Failed to serialize entity ", ex);
}
}
public byte[] serialize(SerializableEntity se)
throws EntityParseException
{
if (!(se instanceof SerializableCollectionAccess))
{
throw new EntityParseException("Cant serialize " + se
+ " as it is not a SerializableCollectionAccess ");
}
SerializableCollectionAccess sc = (SerializableCollectionAccess) se;
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream ds = new DataOutputStream(baos);
ds.write(BYTE_BLOB_ID);
doSerialize(sc, ds);
ds.flush();
baos.flush();
byte[] b = baos.toByteArray();
baos.close();
return b;
}
catch (Exception ex)
{
throw new EntityParseException("Failed to serialize entity ", ex);
}
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.entity.api.serialize.EntitySerializer#accept(java.lang.String)
*/
public boolean accept(byte[] buffer)
{
if ( buffer == null || buffer.length < BYTE_BLOB_ID.length ) {
return false;
}
for (int i = 0; i < BYTE_BLOB_ID.length; i++)
{
if (buffer[i] != BYTE_BLOB_ID[i])
{
return false;
}
}
return true;
}
/**
* @return the timeService
*/
public TimeService getTimeService()
{
return timeService;
}
/**
* @param timeService
* the timeService to set
*/
public void setTimeService(TimeService timeService)
{
this.timeService = timeService;
}
private void doParse(SerializableCollectionAccess sc, DataInputStream ds)
throws EntityParseException, IOException
{
String id = null;
AccessMode access = AccessMode.INHERITED;
boolean hidden = false;
String resourceType = ResourceType.TYPE_FOLDER;
Time releaseDate = null;
Time retractDate = null;
Collection<String> groups = new Vector<String>();
int type = ds.readInt();
if (type == TYPE1)
{
boolean finished = false;
while (!finished)
{
int block = ds.readInt();
switch (block)
{
case BLOCK1:
{
id = ds.readUTF();
if (!ResourceType.TYPE_FOLDER.equals(ds.readUTF()))
{
throw new EntityParseException("Data block is not of tye "
+ ResourceType.TYPE_FOLDER);
}
access = AccessMode.fromString(ds.readUTF());
if (access == null || AccessMode.SITE == access)
{
access = AccessMode.INHERITED;
}
hidden = ds.readBoolean();
resourceType = ResourceType.TYPE_FOLDER;
}
break;
case BLOCK2:
{
long rd = ds.readLong();
if (rd != -1)
{
releaseDate = timeService.newTime(rd);
}
else
{
releaseDate = null;
}
rd = ds.readLong();
if (rd != -1)
{
retractDate = timeService.newTime(rd);
}
else
{
retractDate = null;
}
}
break;
case BLOCK3:
int sz = ds.readInt();
for (int i = 0; i < sz; i++)
{
groups.add(ds.readUTF());
}
if ( sz > 0 )
{
access = AccessMode.GROUPED;
}
break;
case BLOCK4:
baseResourcePropertiesSerializer.parse(sc
.getSerializableProperties(), ds);
break;
case BLOCK_END:
finished = true;
break;
}
}
}
else
{
throw new EntityParseException("Unrecognised Record Type " + type);
}
sc.setSerializableId(id);
sc.setSerializableAccess(access);
sc.setSerializableHidden(hidden);
sc.setSerializableResourceType(resourceType);
sc.setSerializableReleaseDate(releaseDate);
sc.setSerializableRetractDate(retractDate);
sc.setSerializableGroups(groups);
}
private void doSerialize(SerializableCollectionAccess sc, DataOutputStream ds)
throws EntityParseException, IOException
{
String id = sc.getSerializableId();
boolean hidden = sc.getSerializableHidden();
AccessMode access = sc.getSerializableAccess();
Time releaseDate = sc.getSerializableReleaseDate();
Time retractDate = sc.getSerializableRetractDate();
Collection<String> groups = sc.getSerializableGroup();
ds.writeInt(TYPE1);
ds.writeInt(BLOCK1);
ds.writeUTF(id);
ds.writeUTF(ResourceType.TYPE_FOLDER);
if (access == null || AccessMode.SITE == access)
{
access = AccessMode.INHERITED;
}
ds.writeUTF(access.toString());
ds.writeBoolean(hidden);
ds.writeInt(BLOCK2);
if (!hidden && releaseDate != null)
{
ds.writeLong(releaseDate.getTime());
}
else
{
ds.writeLong(-1);
}
if (!hidden && retractDate != null)
{
ds.writeLong(retractDate.getTime());
}
else
{
ds.writeLong(-1);
}
if (groups != null)
{
ds.writeInt(BLOCK3);
ds.writeInt(groups.size());
for (Iterator igroup = groups.iterator(); igroup.hasNext();)
{
String groupRef = (String) igroup.next();
ds.writeUTF(groupRef);
}
}
ds.writeInt(BLOCK4);
baseResourcePropertiesSerializer.serialize(sc.getSerializableProperties(), ds);
ds.writeInt(BLOCK_END);
}
}