Hello Nick, thanks for your response.
The problem with the second approach is:
- When copying one entity from the frozen namespace to the new namespace I need to allocate its ID in the new namespace, otherwise the datastore may generate one ID that is already in use for that Entity in the new namespace. From the docs:
System-allocated ID values are guaranteed unique to the entity group. If you copy an entity from one entity group or namespace to another and wish to preserve the ID part of the key, be sure to allocate the ID first to prevent Datastore from selecting that ID for a future assignment.
So the problem is in the DatastoreService.allocateIdRange() function, when trying to allocate scattered IDs.
Finally I found a workaround that works for me and avoids the bug in the DatastoreService.allocateIdRange(). What I'm doing now is basically make manipulate all the IDs I found (in Entity Keys and in properties that reference a Key) the IDs to short them when copying the Entity to the new Datastore:
private void allocateIdForKey(Key entityK){
if( entityK.getId() > 0 ){
//the entity has an id and it must be allocated
//to avoid collisions in ids
KeyRange range = new KeyRange(
entityK.getParent(),
entityK.getKind(),
entityK.getId(),
entityK.getId()); //throws an exception if the Long ID in the Key is too big
ds.allocateIdRange(range);
}
}
//avoid https://code.google.com/p/googleappengine/issues/detail?id=11541
//by cutting down the long ID if it is too big
private Long getNewId( Long id ){
return id.toString().length() > 6 ?
new Double(Math.ceil(id/2)).longValue() :
id;
}
private Key getNewKey(Key key){
Boolean keyHasStringId = !StringUtil.isNullOrEmpty(key.getName());
if( !keyHasStringId ){ //LONG IDs
//short the ID if needed!! if we don't we'll get an Exceeded Maximum allocated IDs
//exception
Long newId = getNewId(key.getId());
return KeyFactory.createKey(getNewKey(key.getParent()), key.getKind(), newId);
} else { //STRING IDs
return KeyFactory.createKey(getNewKey(key.getParent()), key.getKind(), key.getName());
}
}
@Override
public void map(Entity entity) {
NamespaceManager.set(toNamespace);
//change to the destination namespace, and create the new key for the entity
Key destinationKey = getNewKey(entity.getKey());
Entity destinationEntity = new Entity(destinationKey);
destinationEntity.setPropertiesFrom(entity);
//check entity properties for keys to update them
final Map<String, Object> properties = entity.getProperties();
Set<String> propKeys = properties.keySet();
for (String propKey : propKeys) {
Object property = entity.getProperty(propKey);
if( (property instanceof Key) ){
destinationKey = getNewKey((Key) property);
destinationEntity.setProperty(propKey, destinationKey);
}
}
allocateIdForKey(destinationEntity.getKey());
batcher.put(destinationEntity);
}
Thanks again for your time and effort to help the community!