| /* |
| 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 "CMISAtomPubBaseService.h" |
| #import "CMISAtomPubBaseService+Protected.h" |
| #import "CMISHttpResponse.h" |
| #import "CMISAtomPubServiceDocumentParser.h" |
| #import "CMISConstants.h" |
| #import "CMISAtomEntryParser.h" |
| #import "CMISAtomWorkspace.h" |
| #import "CMISErrors.h" |
| #import "CMISAtomPubObjectByPathUriBuilder.h" |
| #import "CMISAtomPubTypeByIdUriBuilder.h" |
| #import "CMISLinkCache.h" |
| #import "CMISLog.h" |
| #import "CMISAtomEntryWriter.h" |
| |
| @interface CMISAtomPubBaseService () |
| |
| @property (nonatomic, strong, readwrite) CMISBindingSession *bindingSession; |
| @property (nonatomic, strong, readwrite) NSURL *atomPubUrl; |
| |
| @end |
| |
| @implementation CMISAtomPubBaseService |
| |
| |
| - (id)initWithBindingSession:(CMISBindingSession *)session |
| { |
| self = [super init]; |
| if (self) { |
| self.bindingSession = session; |
| self.atomPubUrl = [session objectForKey:kCMISBindingSessionKeyUrl]; |
| } |
| return self; |
| } |
| |
| |
| #pragma mark - |
| #pragma mark Protected methods |
| |
| - (void)retrieveFromCache:(NSString *)cacheKey |
| cmisRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(id object, NSError *error))completionBlock |
| { |
| id object = [self.bindingSession objectForKey:cacheKey]; |
| |
| if (object) { |
| completionBlock(object, nil); |
| return; |
| } else { |
| // if object is nil, first populate cache |
| [self fetchRepositoryInfoWithCMISRequest:cmisRequest completionBlock:^(NSError *error) { |
| id object = [self.bindingSession objectForKey:cacheKey]; |
| if (!object && !error) { |
| // TODO: proper error initialisation |
| error = [[NSError alloc] init]; |
| CMISLogDebug(@"Could not get object from cache with key '%@'", cacheKey); |
| } |
| completionBlock(object, error); |
| }]; |
| } |
| } |
| |
| - (void)fetchRepositoryInfoWithCMISRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(NSError *error))completionBlock |
| { |
| [self retrieveCMISWorkspacesWithCMISRequest:cmisRequest completionBlock:^(NSArray *cmisWorkSpaces, NSError *error) { |
| if (!error) { |
| BOOL repositoryFound = NO; |
| for (CMISAtomWorkspace *workspace in cmisWorkSpaces) { |
| if ([workspace.repositoryInfo.identifier isEqualToString:self.bindingSession.repositoryId]) |
| { |
| repositoryFound = YES; |
| |
| // Cache collections |
| [self.bindingSession setObject:[workspace collectionHrefForCollectionType:kCMISAtomCollectionQuery] forKey:kCMISAtomBindingSessionKeyQueryCollection]; |
| [self.bindingSession setObject:[workspace collectionHrefForCollectionType:kCMISAtomCollectionCheckedout] forKey:kCMISAtomBindingSessionKeyCheckedoutCollection]; |
| |
| |
| // Cache uri's and uri templates |
| CMISAtomPubObjectByIdUriBuilder *objectByIdUriBuilder = [[CMISAtomPubObjectByIdUriBuilder alloc] initWithTemplateUrl:workspace.objectByIdUriTemplate]; |
| [self.bindingSession setObject:objectByIdUriBuilder forKey:kCMISAtomBindingSessionKeyObjectByIdUriBuilder]; |
| |
| CMISAtomPubObjectByPathUriBuilder *objectByPathUriBuilder = [[CMISAtomPubObjectByPathUriBuilder alloc] initWithTemplateUrl:workspace.objectByPathUriTemplate]; |
| [self.bindingSession setObject:objectByPathUriBuilder forKey:kCMISAtomBindingSessionKeyObjectByPathUriBuilder]; |
| |
| CMISAtomPubTypeByIdUriBuilder *typeByIdUriBuilder = [[CMISAtomPubTypeByIdUriBuilder alloc] initWithTemplateUrl:workspace.typeByIdUriTemplate]; |
| [self.bindingSession setObject:typeByIdUriBuilder forKey:kCMISAtomBindingSessionKeyTypeByIdUriBuilder]; |
| |
| [self.bindingSession setObject:workspace.queryUriTemplate forKey:kCMISAtomBindingSessionKeyQueryUri]; |
| |
| break; |
| } |
| } |
| |
| if (!repositoryFound) { |
| CMISLogError(@"No matching repository found for repository id %@", self.bindingSession.repositoryId); |
| // TODO: populate error properly |
| NSString *detailedDescription = [NSString stringWithFormat:@"No matching repository found for repository id %@", self.bindingSession.repositoryId]; |
| error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeNoRepositoryFound detailedDescription:detailedDescription]; |
| } |
| } |
| completionBlock(error); |
| }]; |
| } |
| |
| - (void)retrieveCMISWorkspacesWithCMISRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(NSArray *workspaces, NSError *error))completionBlock |
| { |
| if ([self.bindingSession objectForKey:kCMISSessionKeyWorkspaces]) { |
| completionBlock([self.bindingSession objectForKey:kCMISSessionKeyWorkspaces], nil); |
| } else { |
| [self.bindingSession.networkProvider invokeGET:self.atomPubUrl |
| session:self.bindingSession |
| cmisRequest:cmisRequest |
| completionBlock:^(CMISHttpResponse *httpResponse, NSError *error) { |
| if (httpResponse) { |
| NSData *data = httpResponse.data; |
| // Uncomment to see the service document |
| // NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; |
| // CMISLogDebug(@"Service document: %@", dataString); |
| |
| // Parse the cmis service document |
| if (data) { |
| CMISAtomPubServiceDocumentParser *parser = [[CMISAtomPubServiceDocumentParser alloc] initWithData:data]; |
| NSError *error = nil; |
| if ([parser parseAndReturnError:&error]) { |
| [self.bindingSession setObject:parser.workspaces forKey:kCMISSessionKeyWorkspaces]; |
| } else { |
| CMISLogError(@"Error while parsing service document: %@", error.description); |
| } |
| completionBlock(parser.workspaces, error); |
| } |
| } else { |
| completionBlock(nil, error); |
| } |
| }]; |
| } |
| } |
| |
| - (void)retrieveObjectInternal:(NSString *)objectId |
| cmisRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(CMISObjectData *objectData, NSError *error))completionBlock |
| { |
| return [self retrieveObjectInternal:objectId |
| returnVersion:NOT_PROVIDED |
| filter:@"" |
| relationships:CMISIncludeRelationshipNone |
| includePolicyIds:NO |
| renditionFilder:nil |
| includeACL:NO |
| includeAllowableActions:YES |
| cmisRequest:cmisRequest |
| completionBlock:completionBlock]; |
| } |
| |
| |
| - (void)retrieveObjectInternal:(NSString *)objectId |
| returnVersion:(CMISReturnVersion)returnVersion |
| filter:(NSString *)filter |
| relationships:(CMISIncludeRelationship)relationships |
| includePolicyIds:(BOOL)includePolicyIds |
| renditionFilder:(NSString *)renditionFilter |
| includeACL:(BOOL)includeACL |
| includeAllowableActions:(BOOL)includeAllowableActions |
| cmisRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(CMISObjectData *objectData, NSError *error))completionBlock |
| { |
| [self retrieveFromCache:kCMISAtomBindingSessionKeyObjectByIdUriBuilder |
| cmisRequest:cmisRequest |
| completionBlock:^(id object, NSError *error) { |
| CMISAtomPubObjectByIdUriBuilder *objectByIdUriBuilder = object; |
| objectByIdUriBuilder.objectId = objectId; |
| objectByIdUriBuilder.filter = filter; |
| objectByIdUriBuilder.includeACL = includeACL; |
| objectByIdUriBuilder.includeAllowableActions = includeAllowableActions; |
| objectByIdUriBuilder.includePolicyIds = includePolicyIds; |
| objectByIdUriBuilder.relationships = relationships; |
| objectByIdUriBuilder.renditionFilter = renditionFilter; |
| objectByIdUriBuilder.returnVersion = returnVersion; |
| NSURL *objectIdUrl = [objectByIdUriBuilder buildUrl]; |
| |
| // Execute actual call |
| [self.bindingSession.networkProvider invokeGET:objectIdUrl |
| session:self.bindingSession |
| cmisRequest:cmisRequest |
| completionBlock:^(CMISHttpResponse *httpResponse, NSError *error) { |
| if (httpResponse) { |
| if (httpResponse.statusCode == 200 && httpResponse.data) { |
| CMISObjectData *objectData = nil; |
| NSError *error = nil; |
| CMISAtomEntryParser *parser = [[CMISAtomEntryParser alloc] initWithData:httpResponse.data]; |
| if ([parser parseAndReturnError:&error]) { |
| objectData = parser.objectData; |
| |
| // Add links to link cache |
| CMISLinkCache *linkCache = [self linkCache]; |
| [linkCache addLinks:objectData.linkRelations objectId:objectData.identifier]; |
| } |
| completionBlock(objectData, error); |
| } |
| } else { |
| completionBlock(nil, error); |
| } |
| }]; |
| }]; |
| } |
| |
| - (void)retrieveObjectByPathInternal:(NSString *)path |
| filter:(NSString *)filter |
| relationships:(CMISIncludeRelationship)relationships |
| includePolicyIds:(BOOL)includePolicyIds |
| renditionFilder:(NSString *)renditionFilter |
| includeACL:(BOOL)includeACL |
| includeAllowableActions:(BOOL)includeAllowableActions |
| cmisRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(CMISObjectData *objectData, NSError *error))completionBlock |
| { |
| [self retrieveFromCache:kCMISAtomBindingSessionKeyObjectByPathUriBuilder |
| cmisRequest:cmisRequest |
| completionBlock:^(id object, NSError *error) { |
| CMISAtomPubObjectByPathUriBuilder *objectByPathUriBuilder = object; |
| objectByPathUriBuilder.path = path; |
| objectByPathUriBuilder.filter = filter; |
| objectByPathUriBuilder.includeACL = includeACL; |
| objectByPathUriBuilder.includeAllowableActions = includeAllowableActions; |
| objectByPathUriBuilder.includePolicyIds = includePolicyIds; |
| objectByPathUriBuilder.relationships = relationships; |
| objectByPathUriBuilder.renditionFilter = renditionFilter; |
| |
| // Execute actual call |
| [self.bindingSession.networkProvider invokeGET:[objectByPathUriBuilder buildUrl] |
| session:self.bindingSession |
| cmisRequest:cmisRequest |
| completionBlock:^(CMISHttpResponse *httpResponse, NSError *error) { |
| if (httpResponse) { |
| if (httpResponse.statusCode == 200 && httpResponse.data != nil) { |
| CMISObjectData *objectData = nil; |
| NSError *error = nil; |
| CMISAtomEntryParser *parser = [[CMISAtomEntryParser alloc] initWithData:httpResponse.data]; |
| if ([parser parseAndReturnError:&error]) { |
| objectData = parser.objectData; |
| |
| // Add links to link cache |
| CMISLinkCache *linkCache = [self linkCache]; |
| [linkCache addLinks:objectData.linkRelations objectId:objectData.identifier]; |
| } |
| completionBlock(objectData, error); |
| } |
| } else { |
| completionBlock(nil, error); |
| } |
| }]; |
| }]; |
| } |
| |
| - (CMISLinkCache *)linkCache |
| { |
| CMISLinkCache *linkCache = [self.bindingSession objectForKey:kCMISAtomBindingSessionKeyLinkCache]; |
| if (linkCache == nil) { |
| linkCache = [[CMISLinkCache alloc] initWithBindingSession:self.bindingSession]; |
| [self.bindingSession setObject:linkCache forKey:kCMISAtomBindingSessionKeyLinkCache]; |
| } |
| return linkCache; |
| } |
| |
| - (void)clearCacheFromService |
| { |
| CMISLinkCache *linkCache = [self.bindingSession objectForKey:kCMISAtomBindingSessionKeyLinkCache]; |
| if (linkCache != nil) { |
| [linkCache removeAllLinks]; |
| } |
| } |
| |
| |
| - (void)loadLinkForObjectId:(NSString *)objectId |
| relation:(NSString *)rel |
| cmisRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(NSString *link, NSError *error))completionBlock |
| { |
| [self loadLinkForObjectId:objectId relation:rel type:nil cmisRequest:cmisRequest completionBlock:completionBlock]; |
| } |
| |
| - (void)loadLinkForObjectId:(NSString *)objectId |
| relation:(NSString *)rel |
| type:(NSString *)type |
| cmisRequest:(CMISRequest *)cmisRequest |
| completionBlock:(void (^)(NSString *link, NSError *error))completionBlock |
| { |
| CMISLinkCache *linkCache = [self linkCache]; |
| |
| // Fetch link from cache |
| NSString *link = [linkCache linkForObjectId:objectId relation:rel type:type]; |
| if (link) { |
| completionBlock(link, nil); |
| return;///shall we return nil here |
| } else { |
| // Fetch object, which will trigger the caching of the links |
| [self retrieveObjectInternal:objectId |
| cmisRequest:cmisRequest |
| completionBlock:^(CMISObjectData *objectData, NSError *error) { |
| if (error) { |
| CMISLogDebug(@"Could not retrieve object with id %@", objectId); |
| completionBlock(nil, [CMISErrors cmisError:error cmisErrorCode:kCMISErrorCodeObjectNotFound]); |
| } else { |
| NSString *link = [linkCache linkForObjectId:objectId relation:rel type:type]; |
| if (link == nil) { |
| completionBlock(nil, [CMISErrors createCMISErrorWithCode:kCMISErrorCodeObjectNotFound |
| detailedDescription:[NSString stringWithFormat:@"Could not find link '%@' for object with id %@", rel, objectId]]); |
| } else { |
| completionBlock(link, nil); |
| } |
| } |
| }]; |
| } |
| } |
| |
| - (void)sendAtomEntryXmlToLink:(NSString *)link |
| httpRequestMethod:(CMISHttpRequestMethod)httpRequestMethod |
| properties:(CMISProperties *)properties |
| cmisRequest:(CMISRequest *)request |
| completionBlock:(void (^)(CMISObjectData *objectData, NSError *error))completionBlock |
| { |
| // Validate params |
| if (link == nil) { |
| CMISLogError(@"Must provide link to send atom entry"); |
| if (completionBlock) { |
| completionBlock(nil, [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument detailedDescription:nil]); |
| } |
| return; |
| } |
| |
| // Generate atom entry XML in memory |
| CMISAtomEntryWriter *atomEntryWriter = [[CMISAtomEntryWriter alloc] init]; |
| atomEntryWriter.cmisProperties = properties; |
| atomEntryWriter.generateXmlInMemory = YES; |
| NSString *writeResult = [atomEntryWriter generateAtomEntryXml]; |
| |
| // Execute call |
| [self.bindingSession.networkProvider invoke:[NSURL URLWithString:link] |
| httpMethod:httpRequestMethod |
| session:self.bindingSession |
| body:[writeResult dataUsingEncoding:NSUTF8StringEncoding] |
| headers:[NSDictionary dictionaryWithObject:kCMISMediaTypeEntry forKey:@"Content-type"] |
| cmisRequest:request |
| completionBlock:^(CMISHttpResponse *response, NSError *error) { |
| if (error) { |
| CMISLogError(@"HTTP error when sending atom entry: %@", error); |
| if (completionBlock) { |
| completionBlock(nil, error); |
| } |
| } else if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 204) { |
| if (completionBlock) { |
| CMISAtomEntryParser *atomEntryParser = [[CMISAtomEntryParser alloc] initWithData:response.data]; |
| NSError *parseError = nil; |
| [atomEntryParser parseAndReturnError:&parseError]; |
| if (parseError == nil) { |
| completionBlock(atomEntryParser.objectData, nil); |
| } else { |
| CMISLogError(@"Error while parsing response: %@", [parseError description]); |
| completionBlock(nil, [CMISErrors cmisError:parseError cmisErrorCode:kCMISErrorCodeRuntime]); |
| } |
| } |
| } else { |
| CMISLogError(@"Invalid http response status code when sending atom entry: %ld", (long)response.statusCode); |
| CMISLogError(@"Error content: %@", [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding]); |
| if (completionBlock) { |
| completionBlock(nil, [CMISErrors createCMISErrorWithCode:kCMISErrorCodeRuntime |
| detailedDescription:[NSString stringWithFormat:@"Failed to send atom entry: http status code %li", (long)response.statusCode]]); |
| } |
| } |
| }]; |
| } |
| |
| - (void)sendAtomEntryXmlToLink:(NSString *)link |
| httpRequestMethod:(CMISHttpRequestMethod)httpRequestMethod |
| properties:(CMISProperties *)properties |
| contentInputStream:(NSInputStream *)contentInputStream |
| contentMimeType:(NSString *)contentMimeType |
| bytesExpected:(unsigned long long)bytesExpected |
| cmisRequest:(CMISRequest*)request |
| completionBlock:(void (^)(CMISObjectData *objectData, NSError *error))completionBlock |
| progressBlock:(void (^)(unsigned long long bytesUploaded, unsigned long long bytesTotal))progressBlock |
| { |
| // Validate param |
| if (link == nil) { |
| CMISLogError(@"Must provide link to send atom entry"); |
| if (completionBlock) { |
| completionBlock(nil, [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument detailedDescription:nil]); |
| } |
| return; |
| } |
| |
| // The underlying CMISHttpUploadRequest object generates the atom entry. The base64 encoded content is generated on |
| // the fly to support very large files. |
| [self.bindingSession.networkProvider invoke:[NSURL URLWithString:link] |
| httpMethod:httpRequestMethod |
| session:self.bindingSession |
| inputStream:contentInputStream |
| headers:[NSDictionary dictionaryWithObject:kCMISMediaTypeEntry forKey:@"Content-type"] |
| bytesExpected:bytesExpected |
| cmisRequest:request |
| cmisProperties:properties |
| mimeType:contentMimeType |
| completionBlock:^(CMISHttpResponse *response, NSError *error) { |
| if (error) { |
| CMISLogError(@"HTTP error when sending atom entry: %@", error); |
| if (completionBlock) { |
| completionBlock(nil, error); |
| } |
| } else if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 204) { |
| if (completionBlock) { |
| NSError *parseError = nil; |
| CMISAtomEntryParser *atomEntryParser = [[CMISAtomEntryParser alloc] initWithData:response.data]; |
| [atomEntryParser parseAndReturnError:&parseError]; |
| if (parseError == nil) { |
| completionBlock(atomEntryParser.objectData, nil); |
| } else { |
| CMISLogError(@"Error while parsing response: %@", [parseError description]); |
| completionBlock(nil, [CMISErrors cmisError:parseError cmisErrorCode:kCMISErrorCodeRuntime]); |
| } |
| } |
| } else { |
| CMISLogError(@"Invalid http response status code when sending atom entry: %d", (int)response.statusCode); |
| CMISLogError(@"Error content: %@", [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding]); |
| if (completionBlock) { |
| completionBlock(nil, [CMISErrors createCMISErrorWithCode:kCMISErrorCodeRuntime |
| detailedDescription:[NSString stringWithFormat:@"Failed to send atom entry: http status code %li", (long)response.statusCode]]); |
| } |
| } |
| } |
| progressBlock:progressBlock]; |
| } |
| |
| @end |