/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache 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.apache.org/licenses/LICENSE-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. */ #import "CMISObjectConverter.h" #import "CMISDocument.h" #import "CMISFolder.h" #import "CMISTypeDefinition.h" #import "CMISErrors.h" #import "CMISPropertyDefinition.h" #import "CMISSession.h" #import "CMISDateUtil.h" #import "CMISConstants.h" #import "CMISDictionaryUtil.h" #import "CMISChangeEvents.h" #import "CMISChangeEvent.h" #import "CMISObjectList.h" #import "CMISObjectData.h" #import "CMISChangeEventInfo.h" #import "CMISEnums.h" #import "CMISPolicyIdList.h" #import "CMISItem.h" @interface CMISObjectConverter () @property (nonatomic, weak) CMISSession *session; @end @implementation CMISObjectConverter - (id)initWithSession:(CMISSession *)session { self = [super init]; if (self) { self.session = session; } return self; } - (void)convertObject:(CMISObjectData *)objectData completionBlock:(void (^)(CMISObject *object, NSError *error))completionBlock { CMISObject *object = nil; if (objectData.baseType == CMISBaseTypeDocument) { object = [[CMISDocument alloc] initWithObjectData:objectData session:self.session]; } else if (objectData.baseType == CMISBaseTypeFolder) { object = [[CMISFolder alloc] initWithObjectData:objectData session:self.session]; } else if (objectData.baseType == CMISBaseTypeItem) { object = [[CMISItem alloc] initWithObjectData:objectData session:self.session]; } if (object) { [object fetchTypeDefinitionWithCompletionBlock:^(NSError *error) { completionBlock(object, error); }]; } else { NSError *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeNotSupported detailedDescription:[NSString stringWithFormat:@"Base type '%ld' not supported", (long)objectData.baseType]]; completionBlock(nil, error); } } - (void)internalConvertObject:(NSArray *)objectDatas position:(NSInteger)position completionBlock:(void (^)(NSMutableArray *objects, NSError *error))completionBlock { [self convertObject:[objectDatas objectAtIndex:position] completionBlock:^(CMISObject *object, NSError *error) { if(error){ completionBlock(nil, error); } else { if (position == 0) { NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:objectDatas.count]; if (object != nil) { [objects addObject:object]; } completionBlock(objects, error); } else { [self internalConvertObject:objectDatas position:(position - 1) completionBlock:^(NSMutableArray *objects, NSError *error) { if (object != nil) { [objects addObject:object]; } completionBlock(objects, error); }]; } } }]; } - (void)convertObjects:(NSArray *)objectDatas completionBlock:(void (^)(NSArray *objects, NSError *error))completionBlock { if (objectDatas.count > 0) { [self internalConvertObject:objectDatas position:(objectDatas.count - 1) // start recursion with last item completionBlock:^(NSMutableArray *objects, NSError *error) { completionBlock(objects, error); }]; } else { completionBlock([[NSArray alloc] init], nil); } } - (void)convertProperties:(NSDictionary *)properties forObjectTypeId:(NSString *)objectTypeId completionBlock:(void (^)(CMISProperties *convertedProperties, NSError *error))completionBlock { [self internalNormalConvertProperties:properties objectTypeId:objectTypeId completionBlock:completionBlock]; } - (void)internalNormalConvertProperties:(NSDictionary *)properties typeDefinition:(CMISTypeDefinition *)typeDefinition completionBlock:(void (^)(CMISProperties *convertedProperties, NSError *error))completionBlock { NSArray *typeDefinitions = nil; if (typeDefinition) { typeDefinitions = [NSArray arrayWithObject:typeDefinition]; } [self internalNormalConvertProperties:properties typeDefinitions:typeDefinitions completionBlock:completionBlock]; } - (void)internalNormalConvertProperties:(NSDictionary *)properties typeDefinitions:(NSArray *)typeDefinitions completionBlock:(void (^)(CMISProperties *convertedProperties, NSError *error))completionBlock { CMISProperties *convertedProperties = [[CMISProperties alloc] init]; for (NSString *propertyId in properties) { id propertyValue = [properties objectForKey:propertyId]; // If the value is already a CMISPropertyData, we don't need to do anything if ([propertyValue isKindOfClass:[CMISPropertyData class]]) { [convertedProperties addProperty:(CMISPropertyData *)propertyValue]; } else { // Convert to CMISPropertyData based on the string CMISPropertyDefinition *propertyDefinition = [self propertyDefinitionFromTypeDefinitions:typeDefinitions propertyId:propertyId]; if (propertyDefinition == nil) { NSError *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument detailedDescription:[NSString stringWithFormat:@"Invalid property '%@' for this object type", propertyId]]; completionBlock(nil, error); return; } Class expectedType = nil; BOOL validType = YES; if (propertyValue == [NSNull null]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:nil type:propertyDefinition.propertyType]]; } else { switch (propertyDefinition.propertyType) { case(CMISPropertyTypeString): { expectedType = [NSString class]; if ([propertyValue isKindOfClass:expectedType]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId stringValue:propertyValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeBoolean): { expectedType = [NSNumber class]; if ([propertyValue isKindOfClass:expectedType]) { BOOL boolValue = ((NSNumber *) propertyValue).boolValue; [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId boolValue:boolValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeInteger): { expectedType = [NSNumber class]; if ([propertyValue isKindOfClass:expectedType]) { NSInteger intValue = ((NSNumber *) propertyValue).integerValue; [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId integerValue:intValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeDecimal): { expectedType = [NSNumber class]; if ([propertyValue isKindOfClass:expectedType]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId decimalValue:propertyValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeId): { expectedType = [NSString class]; if ([propertyValue isKindOfClass:expectedType]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId idValue:propertyValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeDateTime): { if ([propertyValue isKindOfClass:[NSString class]]) { propertyValue = [CMISDateUtil dateFromString:propertyValue]; } expectedType = [NSDate class]; if ([propertyValue isKindOfClass:expectedType]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId dateTimeValue:propertyValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (__strong id propertyValueItemValue in propertyValue) { if ([propertyValueItemValue isKindOfClass:[NSString class]]) { propertyValueItemValue = [CMISDateUtil dateFromString:propertyValueItemValue]; } if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeUri): { if ([propertyValue isKindOfClass:[NSString class]]) { propertyValue = [NSURL URLWithString:propertyValue]; } expectedType = [NSURL class]; if ([propertyValue isKindOfClass:expectedType]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId uriValue:propertyValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (__strong id propertyValueItemValue in propertyValue) { if ([propertyValueItemValue isKindOfClass:[NSString class]]) { propertyValueItemValue = [NSURL URLWithString:propertyValueItemValue]; } if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } case(CMISPropertyTypeHtml): { expectedType = [NSString class]; if ([propertyValue isKindOfClass:expectedType]) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId htmlValue:propertyValue]]; } else if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:expectedType]) { validType = NO; break; } } if (validType) { [convertedProperties addProperty:[CMISPropertyData createPropertyForId:propertyId arrayValue:propertyValue type:propertyDefinition.propertyType]]; } } else { validType = NO; } break; } default: { NSError *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument detailedDescription:[NSString stringWithFormat:@"Unsupported: cannot convert property type %li", (long)propertyDefinition.propertyType]]; completionBlock(nil, error); return; } } } if (!validType) { NSError *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument detailedDescription:[NSString stringWithFormat:@"Property value for %@ should be of type '%@'", propertyId, expectedType]]; completionBlock(nil, error); return; } } } completionBlock(convertedProperties, nil); } -(CMISPropertyDefinition*)propertyDefinitionFromTypeDefinitions:(NSArray *)typeDefinitions propertyId:(NSString*)propertyId { for (CMISTypeDefinition* typeDefinition in typeDefinitions) { CMISPropertyDefinition *propertyDefinition = [typeDefinition propertyDefinitionForId:propertyId]; if (propertyDefinition) { return propertyDefinition; } } return nil; } - (void)internalNormalConvertProperties:(NSDictionary *)properties objectTypeId:(NSString *)objectTypeId completionBlock:(void (^)(CMISProperties *convertedProperties, NSError *error))completionBlock { // Validate params if (!properties) { completionBlock(nil, nil); return; } BOOL onlyPropertyData = YES; for (id propertyValue in properties.objectEnumerator) { if (![propertyValue isKindOfClass:[CMISPropertyData class]]) { if ([propertyValue isKindOfClass:[NSArray class]]) { for (id propertyValueItemValue in propertyValue) { if (![propertyValueItemValue isKindOfClass:[CMISPropertyData class]]) { onlyPropertyData = NO; break; } } } else { onlyPropertyData = NO; } break; } } // Convert properties if (onlyPropertyData) { [self internalNormalConvertProperties:properties typeDefinition:nil // not needed because all properties are of type CMISPropertyData completionBlock:completionBlock]; } else { //get secondary object type definitions - if available NSString *propertyId = kCMISPropertySecondaryObjectTypeIds; id secondaryObjectTypeIds = [properties valueForKey:propertyId]; if(secondaryObjectTypeIds) { Class expectedType = nil; BOOL validType = YES; //verify types expectedType = [NSArray class]; if([secondaryObjectTypeIds isKindOfClass:expectedType]){ expectedType = [NSString class]; for (id secondaryObjectTypeId in secondaryObjectTypeIds) { propertyId = secondaryObjectTypeId; if(![secondaryObjectTypeId isKindOfClass:expectedType]){ validType = NO; break; } } } else { validType = NO; } if (!validType) { NSError *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument detailedDescription:[NSString stringWithFormat:@"Property value for %@ should be of type '%@'", propertyId, expectedType]]; completionBlock(nil, error); return; } NSMutableArray *objectTypeIds = [NSMutableArray arrayWithObject:objectTypeId]; [objectTypeIds addObjectsFromArray:secondaryObjectTypeIds]; [self retrieveTypeDefinitions:objectTypeIds completionBlock:^(NSArray *typeDefinitions, NSError *error) { if (error) { completionBlock(nil, [CMISErrors cmisError:error cmisErrorCode:kCMISErrorCodeRuntime]); } else { [self internalNormalConvertProperties:properties typeDefinitions:typeDefinitions completionBlock:completionBlock]; } }]; } else { [self.session retrieveTypeDefinition:objectTypeId completionBlock:^(CMISTypeDefinition *typeDefinition, NSError *error) { if (error) { completionBlock(nil, [CMISErrors cmisError:error cmisErrorCode:kCMISErrorCodeRuntime]); } else { [self internalNormalConvertProperties:properties typeDefinition:typeDefinition completionBlock:completionBlock]; } }]; } } } - (void)retrieveTypeDefinitions:(NSArray *)objectTypeIds position:(NSInteger)position completionBlock:(void (^)(NSMutableArray *typeDefinitions, NSError *error))completionBlock { [self.session retrieveTypeDefinition:[objectTypeIds objectAtIndex:position] completionBlock:^(CMISTypeDefinition *typeDefinition, NSError *error) { if(error){ completionBlock(nil, error); } else { if (position == 0) { NSMutableArray *typeDefinitions = [[NSMutableArray alloc] initWithCapacity:objectTypeIds.count]; [typeDefinitions addObject:typeDefinition]; completionBlock(typeDefinitions, error); } else { [self retrieveTypeDefinitions:objectTypeIds position:(position - 1) completionBlock:^(NSMutableArray *typeDefinitions, NSError *error) { [typeDefinitions addObject:typeDefinition]; completionBlock(typeDefinitions, error); }]; } } }]; } - (void)retrieveTypeDefinitions:(NSArray *)objectTypeIds completionBlock:(void (^)(NSArray *typeDefinitions, NSError *error))completionBlock { if (objectTypeIds.count > 0) { [self retrieveTypeDefinitions:objectTypeIds position:(objectTypeIds.count - 1) // start recursion with last item completionBlock:^(NSMutableArray *typeDefinitions, NSError *error) { completionBlock(typeDefinitions, error); }]; } else { completionBlock([[NSArray alloc] init], nil); } } + (NSArray *)convertExtensions:(NSDictionary *)source cmisKeys:(NSSet *)cmisKeys { if (!source) { return nil; } NSMutableArray *extensions = nil; // array of CMISExtensionElement's for (NSString *key in source.keyEnumerator) { if ([cmisKeys containsObject:key]) { continue; } if (!extensions) { extensions = [[NSMutableArray alloc] init]; } id value = [source cmis_objectForKeyNotNull:key]; if ([value isKindOfClass:NSDictionary.class]) { [extensions addObject:[[CMISExtensionElement alloc] initNodeWithName:key namespaceUri:nil attributes:nil children:[CMISObjectConverter convertExtension:value]]]; } else if ([value isKindOfClass:NSArray.class]) { [extensions addObjectsFromArray:[CMISObjectConverter convertExtension: key fromArray:value]]; } else { [extensions addObject:[[CMISExtensionElement alloc] initLeafWithName:key namespaceUri:nil attributes:nil value:value]]; } } return extensions; } + (NSArray *)convertExtension:(NSDictionary *)dictionary { if (!dictionary) { return nil; } NSMutableArray *extensions = [[NSMutableArray alloc] init]; // array of CMISExtensionElement's for (NSString *key in dictionary.keyEnumerator) { id value = [dictionary cmis_objectForKeyNotNull:key]; if ([value isKindOfClass:NSDictionary.class]) { [extensions addObject:[[CMISExtensionElement alloc] initNodeWithName:key namespaceUri:nil attributes:nil children:[CMISObjectConverter convertExtension:value]]]; } else if ([value isKindOfClass:NSArray.class]) { [extensions addObjectsFromArray:[CMISObjectConverter convertExtension: key fromArray:value]]; } else { [extensions addObject:[[CMISExtensionElement alloc] initLeafWithName:key namespaceUri:nil attributes:nil value:value]]; } } return extensions; } + (NSArray *)convertExtension:(NSString *)key fromArray:(NSArray *)array { if (!array) { return nil; } NSMutableArray *extensions = [[NSMutableArray alloc] init]; // array of CMISExtensionElement's for (id element in array) { if ([element isKindOfClass:NSDictionary.class]) { [extensions addObject:[[CMISExtensionElement alloc] initNodeWithName:key namespaceUri:nil attributes:nil children:[CMISObjectConverter convertExtension:element]]]; } else if ([element isKindOfClass:NSArray.class]) { [extensions addObjectsFromArray:[CMISObjectConverter convertExtension: key fromArray:element]]; } else { [extensions addObject:[[CMISExtensionElement alloc] initLeafWithName:key namespaceUri:nil attributes:nil value:element]]; } } return extensions; } + (CMISChangeEvents *)convertChangeEvents:(NSString *)changeLogToken objectList:(CMISObjectList *)objectList { if (objectList == nil) { return nil; } NSMutableArray *events = [NSMutableArray new]; for (CMISObjectData *objectData in objectList.objects) { CMISChangeEvent *changeEvent = [self convertChangeEvent:objectData]; [events addObject:changeEvent]; } CMISChangeEvents *changeEvents = [CMISChangeEvents new]; changeEvents.hasMoreItems = objectList.hasMoreItems; changeEvents.numItems = objectList.numItems; changeEvents.changeEvents = [events copy]; changeEvents.latestChangeLogToken = changeLogToken; return changeEvents; } + (CMISChangeEvent *)convertChangeEvent:(CMISObjectData *)objectData { CMISChangeEvent *changeEvent = [CMISChangeEvent new]; NSMutableDictionary *properties = nil; if (objectData.changeEventInfo) { changeEvent.changeType = objectData.changeEventInfo.changeType; changeEvent.changeTime = objectData.changeEventInfo.changeTime; } if (objectData.properties.propertyList) { properties = [NSMutableDictionary new]; for (CMISPropertyData *property in objectData.properties.propertyList) { [properties setObject:property.values forKey:property.identifier]; } changeEvent.properties = [properties copy]; NSArray *objectIdList = [properties objectForKey:kCMISPropertyObjectId]; if (objectIdList.count > 0) { changeEvent.objectId = objectIdList[0]; } changeEvent.policyIds = objectData.policyIds.policyIds; changeEvent.acl = objectData.acl; } return changeEvent; } @end