File Coverage

blib/lib/WebService/Cmis/Repository.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package WebService::Cmis::Repository;
2              
3             =head1 NAME
4              
5             WebService::Cmis::Repository - Representation of a cmis repository
6              
7             =head1 DESCRIPTION
8              
9             After fetching a L object, fetching the
10             repository is the next thing to do in most cases using L.
11              
12             my $repo = WebService::Cmis::getClient->getRepository('repositoryId');
13              
14             =cut
15              
16 1     1   21083 use strict;
  1         2  
  1         48  
17 1     1   7 use warnings;
  1         2  
  1         38  
18              
19 1     1   5 use WebService::Cmis qw(:namespaces :collections :utils :relations :contenttypes);
  1         2  
  1         419  
20 1     1   509 use XML::LibXML qw(:libxml);
  0            
  0            
21             use Error qw(:try);
22             use WebService::Cmis::NotImplementedException ();
23             use WebService::Cmis::NotSupportedException ();
24             use WebService::Cmis::ClientException ();
25              
26             our $CMIS_XPATH_REPOSITORYINFO = new XML::LibXML::XPathExpression('./*[local-name() = "repositoryInfo" and namespace-uri() = "'.CMISRA_NS.'"]/*[local-name() != "capabilities" and local-name() != "aclCapability" and namespace-uri() = "'.CMIS_NS.'"]');
27             our $CMIS_XPATH_CAPABILITIES = new XML::LibXML::XPathExpression('./*[local-name() = "repositoryInfo" and namespace-uri() = "'.CMISRA_NS.'"]/*[local-name() = "capabilities" and namespace-uri() = "'.CMIS_NS.'"]/*');
28             our $CMIS_XPATH_SUPPORTED_PERMISSIONS = new XML::LibXML::XPathExpression('./*[local-name() = "repositoryInfo" and namespace-uri() = "'.CMISRA_NS.'"]/*[local-name() = "aclCapability" and namespace-uri() = "'.CMIS_NS.'"]/*[local-name() = "supportedPermissions" and namespace-uri() = "'.CMIS_NS.'"]');
29             our $CMIS_XPATH_PROPAGATION = new XML::LibXML::XPathExpression('./*[local-name() = "repositoryInfo" and namespace-uri() = "'.CMISRA_NS.'"]/*[local-name() = "aclCapability" and namespace-uri() = "'.CMIS_NS.'"]/*[local-name() = "propagation" and namespace-uri() = "'.CMIS_NS.'"]');
30             our $CMIS_XPATH_PERMISSION_DEFINITION = new XML::LibXML::XPathExpression('./*[local-name() = "repositoryInfo" and namespace-uri() = "'.CMISRA_NS.'"]/*[local-name() = "aclCapability" and namespace-uri() = "'.CMIS_NS.'"]/*[local-name() = "permissions" and namespace-uri() = "'.CMIS_NS.'"]');
31             our $CMIS_XPATH_PERMISSION_MAP = new XML::LibXML::XPathExpression('./*[local-name() = "repositoryInfo" and namespace-uri() = "'.CMISRA_NS.'"]/*[local-name() = "aclCapability" and namespace-uri() = "'.CMIS_NS.'"]/*[local-name() = "mapping" and namespace-uri() = "'.CMIS_NS.'"]');
32             our $CMIS_XPATH_URITEMPLATE = new XML::LibXML::XPathExpression('./*[local-name() = "uritemplate" and namespace-uri() = "'.CMISRA_NS.'"]');
33             our $CMIS_XPATH_COLLECTION = new XML::LibXML::XPathExpression('./*[local-name() = "collection" and namespace-uri()="'.APP_NS.'" and @href]');
34              
35             =head1 METHODS
36              
37             =over 4
38              
39             =item new($client, $xmlDoc)
40              
41             Create a new repository object using the given $client and loading
42             the information stored in the $xmlDoc.
43              
44             =cut
45              
46             sub new {
47             my ($class, $client, $xmlDoc) = @_;
48              
49             my $this = bless({
50             client => $client,
51             xmlDoc => $xmlDoc,
52             }, $class);
53              
54             $this->_initData;
55            
56             return $this;
57             }
58              
59             =item getClient() -> L
60              
61             returns the the client object used to communicate with the repository.
62              
63             =cut
64              
65             sub getClient {
66             return $_[0]->{client};
67             }
68              
69              
70             # internal function to reset cached data
71             sub _initData {
72             my $this = shift;
73              
74             $this->{repositoryInfo} = undef;
75             $this->{capabilities} = undef;
76             $this->{uriTemplates} = undef;
77             $this->{permDefs} = undef;
78             $this->{permMap} = undef;
79             $this->{permissions} = undef;
80             $this->{propagation} = undef;
81             $this->{uriTempaltes} = undef;
82             $this->{collectionLink} = undef;
83             $this->{typeDefs} = undef;
84             }
85              
86             sub DESTROY {
87             my $this = shift;
88              
89             #print STDERR "called Repository::DESTROY\n";
90            
91             undef $this->{repositoryInfo};
92             undef $this->{capabilities};
93             undef $this->{uriTemplates};
94             undef $this->{permDefs};
95             undef $this->{permMap};
96             undef $this->{permissions};
97             undef $this->{propagation};
98             undef $this->{uriTempaltes};
99             undef $this->{collectionLink};
100             undef $this->{typeDefs};
101             undef $this->{xmlDoc};
102             undef $this->{client};
103             undef $this->{fileMage};
104             }
105              
106             =item toString()
107              
108             return a string representation of this repository
109              
110             =cut
111              
112             sub toString {
113             my $this = shift;
114             return $this->getRepositoryId;
115             }
116              
117             =item reload()
118              
119             This method will re-fetch the repository's XML data from the CMIS
120             repository.
121              
122             =cut
123              
124             sub reload {
125             my $this = shift;
126              
127             $this->{xmlDoc} = $this->{client}->get;
128             $this->_initData;
129             }
130              
131             #internal helper to make sure the xmlDoc is loaded
132             sub _xmlDoc {
133             $_[0]->reload unless defined $_[0]->{xmlDoc};
134             return $_[0]->{xmlDoc};
135             }
136              
137             =item getRepositoryId()
138              
139             returns this repository's ID
140              
141             =cut
142              
143             sub getRepositoryId {
144             return $_[0]->getRepositoryInfo->{repositoryId};
145             }
146              
147             =item getRepositoryName()
148              
149             returns this repository's name
150              
151             =cut
152              
153             sub getRepositoryName {
154             return $_[0]->getRepositoryInfo->{repositoryName};
155             }
156              
157             =item getRepositoryInfo() -> %info
158              
159             returns this repository's info record
160              
161             See CMIS specification document 2.2.2.2 getRepositoryInfo
162              
163             =cut
164              
165             sub getRepositoryInfo {
166             my $this = shift;
167              
168             unless (defined $this->{repositoryInfo}) {
169             $this->{repositoryInfo}{$_->localname} = $_->string_value
170             foreach $this->_xmlDoc->findnodes($CMIS_XPATH_REPOSITORYINFO);
171             }
172              
173             return $this->{repositoryInfo};
174             }
175              
176             =item getCapabilities() -> %caps
177              
178             returns this repository's capabilities
179              
180             =cut
181              
182             sub getCapabilities {
183             my $this = shift;
184              
185             unless (defined $this->{capabilities}) {
186             require WebService::Cmis::Property::Boolean;
187              
188             $this->{capabilities} = {};
189             foreach my $node ($this->_xmlDoc->findnodes($CMIS_XPATH_CAPABILITIES)) {
190             my $key = $node->localname;
191             $key =~ s/^capability//;
192              
193             my $val = $node->string_value;
194             $val = WebService::Cmis::Property::Boolean->parse($val) if $val =~ /^(true|false)$/;
195             $this->{capabilities}{$key} = $val;
196             }
197             }
198              
199             return $this->{capabilities};
200             }
201              
202             =item getSupportedPermissions() -> $permissions
203              
204             returns this repository's supported permissions.
205             values are:
206              
207             basic: indicates that the CMIS Basic permissions are supported
208             repository: indicates that repository specific permissions are supported
209             both: indicates that both CMIS basic permissions and repository specific permissions are supported
210              
211             =cut
212              
213             sub getSupportedPermissions {
214             my $this = shift;
215              
216             unless ($this->getCapabilities()->{'ACL'}) {
217             throw WebService::Cmis::NotSupportedException("This repository does not support ACLs");
218             }
219              
220             unless (defined $this->{permissions}) {
221             $this->{permissions} = $this->_xmlDoc->findvalue($CMIS_XPATH_SUPPORTED_PERMISSIONS);
222             }
223              
224             return $this->{permissions};
225             }
226              
227             =item getPropagation() -> $string
228              
229             returns the value of the cmis:propagation element. Valid values are:
230              
231             objectonly: indicates that the repository is able to apply ACEs
232             without changing the ACLs of other objects
233              
234             propagate: indicates that the repository is able to apply ACEs to a
235             given object and propagate this change to all inheriting objects
236              
237             =cut
238              
239             sub getPropagation {
240             my $this = shift;
241              
242             unless ($this->getCapabilities()->{'ACL'}) {
243             throw WebService::Cmis::NotSupportedException("This repository does not support ACLs");
244             }
245              
246             unless (defined $this->{propagation}) {
247             $this->{propagation} = $this->_xmlDoc->findvalue($CMIS_XPATH_PROPAGATION);
248             }
249              
250             return $this->{propagation};
251             }
252              
253             =item getPermissionDefinitions() -> %permDefs
254              
255             Returns a hash of permission definitions for this repository. The key is the
256             permission string or technical name of the permission and the value is the
257             permission description.
258              
259             =cut
260              
261             sub getPermissionDefinitions {
262             my $this = shift;
263              
264             unless ($this->getCapabilities()->{'ACL'}) {
265             throw WebService::Cmis::NotSupportedException("This repository does not support ACLs");
266             }
267              
268             unless (defined $this->{permDefs}) {
269            
270             foreach my $node ($this->_xmlDoc->findnodes($CMIS_XPATH_PERMISSION_DEFINITION)) {
271             my ($permNode) = $node->getElementsByTagNameNS(CMIS_NS, 'permission'); # these two getElementsByTagNameNS are ok
272             my ($descNode) = $node->getElementsByTagNameNS(CMIS_NS, 'description');
273            
274             if (defined $permNode && defined $descNode) {
275             # alfresco has got a detailed sub node
276             $this->{permDefs}{$permNode->string_value} = $descNode->string_value;
277             } else {
278             # TODO: nuxeo looks differernt down here
279             }
280             }
281             }
282              
283             return $this->{permDefs};
284             }
285              
286             =item getPermissionMap() -> %permMap
287              
288             returns a hash representing the permission mapping table where
289             each key is a permission key string and each value is a list of one or
290             more permissions the principal must have to perform the operation.
291              
292             =cut
293              
294             sub getPermissionMap {
295             my $this = shift;
296              
297             unless ($this->getCapabilities()->{'ACL'}) {
298             throw WebService::Cmis::NotSupportedException("This repository does not support ACLs");
299             }
300              
301             unless (defined $this->{permMap}) {
302            
303             foreach my $node ($this->_xmlDoc->findnodes($CMIS_XPATH_PERMISSION_MAP)) {
304             my @permList = ();
305             my ($keyNode) = $node->getElementsByTagNameNS(CMIS_NS, 'key'); # these two getElementsByTagNameNS are ok
306             foreach my $permNode ($node->getElementsByTagNameNS(CMIS_NS, 'permission')) {
307             push @permList, $permNode->string_value;
308             }
309             $this->{permMap}{$keyNode->string_value} = \@permList;
310             }
311             }
312              
313             return $this->{permMap}
314             }
315              
316             =item getUriTemplates() -> %templates
317              
318             returns a hash of URI templates the repository service knows about.
319              
320             =cut
321              
322             sub getUriTemplates {
323             my $this = shift;
324              
325             unless (defined $this->{uriTemplates}) {
326              
327             foreach my $node ($this->_xmlDoc->findnodes($CMIS_XPATH_URITEMPLATE)) {
328             my $template;
329             my $type;
330             my $mediaType;
331              
332             foreach my $subNode ($node->childNodes) {
333             next if $subNode->nodeType != XML_ELEMENT_NODE;
334             my $localName = $subNode->localname;
335             if ($localName eq 'template') {
336             $template = $subNode->string_value;
337             } elsif ($localName eq 'type') {
338             $type = $subNode->string_value;
339             } elsif ($localName eq 'mediatype') {
340             $mediaType = $subNode->string_value;
341             }
342             last if defined $template && defined $type && defined $mediaType;
343             }
344             $this->{uriTemplates}{$type} = {
345             template => $template,
346             type => $type,
347             mediatype => $mediaType,
348             };
349             }
350             }
351              
352             return $this->{uriTemplates};
353             }
354              
355             =item getUriTemplate($type) -> $template
356              
357             returns an uri template for the given type
358              
359             =cut
360              
361             sub getUriTemplate {
362             my ($this, $type) = @_;
363              
364             return $this->getUriTemplates()->{$type}->{template};
365             }
366              
367             =item getRootFolder() -> $folder
368              
369             returns the root folder of the repository
370              
371             =cut
372              
373             sub getRootFolder {
374             my $this = shift;
375              
376             my $id = $this->getRepositoryInfo->{'rootFolderId'};
377              
378             return $this->getFolder($id) if $id;
379              
380             # some repos don't advertise the root folder
381             return $this->getObjectByPath("/");
382             }
383              
384             =item getFolder($id) -> $foldeer
385              
386             returns the a folder object of the given id
387              
388             =cut
389              
390             sub getFolder {
391             my ($this, $id) = @_;
392              
393             require WebService::Cmis::Folder;
394             return new WebService::Cmis::Folder(repository=>$this, id=>$id);
395             }
396              
397             =item getCollection($collectionType, %args) -> $atomFeed
398              
399             returns a AtomFeed of objects returned for the specified collection.
400              
401             If the query collection is requested, an exception will be throwd.
402             That collection isn't meant to be retrieved.
403              
404             =cut
405              
406             sub getCollection {
407             my $this = shift;
408             my $collectionType = shift;
409              
410             if ($collectionType eq QUERY_COLL) {
411             throw Error::Simple("query collection not supported"); # SMELL: use a custom exception
412             }
413              
414             my $link = $this->getCollectionLink($collectionType);
415             my $result = $this->{client}->get($link, @_);
416             #_writeCmisDebug("result=".$result->toString);
417              
418             # return the result set
419             if ($collectionType eq TYPES_COLL) {
420             require WebService::Cmis::AtomFeed::ObjectTypes;
421             return new WebService::Cmis::AtomFeed::ObjectTypes(repository=>$this, xmlDoc=>$result);
422             } else {
423             require WebService::Cmis::AtomFeed::Objects;
424             return new WebService::Cmis::AtomFeed::Objects(repository=>$this, xmlDoc=>$result);
425             }
426             }
427              
428             =item getTypeDefinition($typeId) -> $objectType
429              
430             returns an ObjectType object for the specified object type id.
431              
432             See CMIS specification document 2.2.2.5 getTypeDefinition
433              
434             folderType = repo.getTypeDefinition('cmis:folder')
435              
436             =cut
437              
438             sub getTypeDefinition {
439             my ($this, $id) = @_;
440              
441             require WebService::Cmis::ObjectType;
442             my $objectType = new WebService::Cmis::ObjectType(repository=>$this, id=>$id);
443             $objectType->reload;
444             return $objectType;
445             }
446              
447             =item getCollectionLink($collectionType) -> $href
448              
449             returns the link HREF from the specified collectionType
450             (CHECKED_OUT_COLL, for example).
451              
452             =cut
453              
454             sub getCollectionLink {
455             my ($this, $collectionType) = @_;
456              
457             unless ($this->{collectionLink}) {
458            
459             foreach my $node ($this->_xmlDoc->findnodes($CMIS_XPATH_COLLECTION)) {
460             my $href = $node->attributes->getNamedItem('href')->value;
461             foreach my $subNode ($node->childNodes) {
462             next unless $subNode->nodeType == XML_ELEMENT_NODE && $subNode->localname eq 'collectionType';
463             $this->{collectionLink}{$subNode->string_value} = $href;
464             }
465             }
466             #_writeCmisDebug("collection link for $collectionType: $this->{collectionLink}{$collectionType}");
467             }
468              
469             return $this->{collectionLink}{$collectionType};
470             }
471              
472             =item getLink($relation) -> $href
473              
474             returns the HREF attribute of an Atom link element for the
475             specified rel.
476              
477             =cut
478              
479             sub getLink {
480             my ($this, $relation) = @_;
481              
482             my $href = $this->_xmlDoc->find('./*[local-name() = "link" and namespace-uri() = "'.ATOM_NS.'" and @rel="'.$relation.'"]/@href');
483             return "".$href if $href;
484             return;
485             }
486              
487             =item getObjectByPath($path, %params) -> $cmisObj
488              
489             returns an object given the path to the object.
490              
491             my $doc = $repo->getObjectByPath("/User homes/jeff/sample.pdf");
492             my $title = $doc->getTitle();
493              
494             These optional arguments are supported:
495              
496             =over 4
497              
498             =item filter: See section 2.2.1.2.1 Properties.
499              
500             =item includeAllowableActions: See section 2.2.1.2.6 Allowable Actions.
501              
502             =item includeRelationships: See section 2.2.1.2.2 Relationships.
503              
504             =item renditionFilter: See section 2.2.1.2.4 Renditions.
505              
506             =item includePolicyIds: See section 2.2.1.2.2 Relationships.
507              
508             =item includeACL: See section 2.2.1.2.5 ACLs.
509              
510             =back
511              
512             See CMIS specification document 2.2.4.9 getObjectByPath
513              
514             =cut
515              
516             sub getObjectByPath {
517             my $this = shift;
518             my $path = shift;
519             my %params = @_;
520              
521             # get the uritemplate
522             my $template = $this->getUriTemplate('objectbypath');
523              
524             require WebService::Cmis::Property::Boolean;
525              
526             $path ||= '/';
527             $template =~ s/{path}/delete $params{path}||$path/ge;
528             $template =~ s/{filter}/delete $params{filter}||''/ge;
529             $template =~ s/{includeAllowableActions}/WebService::Cmis::Property::Boolean->unparse(delete $params{includeAllowableActions}||'false')/ge;
530             $template =~ s/{includePolicyIds}/WebService::Cmis::Property::Boolean->unparse(delete $params{includePolicyIds}||'false')/ge;
531             $template =~ s/{includeRelationships}/WebService::Cmis::Property::Boolean->unparse(delete $params{includeRelationships}||'')/ge;
532             $template =~ s/{includeACL}/WebService::Cmis::Property::Boolean->unparse(delete $params{includeACL}||'false')/ge;
533             $template =~ s/{renditionFilter}/delete $params{renditionFilter}||''/ge;
534              
535             #print STDERR "template=$template\n";
536              
537             # do a GET against the URL
538             my $result;
539            
540             try {
541             $result = $this->{client}->get($template, @_);
542             } catch WebService::Cmis::ClientException with {
543             # ignore
544             };
545              
546             return unless $result;
547              
548             require WebService::Cmis::Object;
549             return new WebService::Cmis::Object(repository=>$this, xmlDoc=>$result, extra_params=>\%params);
550             }
551              
552             =item getObject($id, %params) -> $cmisObj
553              
554             returns an object given the specified object ID.
555              
556             See CMIS specification document 2.2.4.7 getObject
557              
558             =cut
559              
560             sub getObject {
561             my $this = shift;
562             my $id = shift;
563             my %params = @_;
564              
565             require WebService::Cmis::Object;
566              
567             my $obj;
568             try {
569             $obj = new WebService::Cmis::Object(repository=>$this, id=>$id, extra_params=>\%params);
570             } catch WebService::Cmis::ClientException with {
571             # ignore
572             };
573              
574             return $obj;
575             }
576              
577             =item getCheckedOutDocs(%params) -> $atomFeed
578              
579             returns a result set of cmis objects that
580             are currently checked out.
581              
582             See CMIS specification document 2.2.3.6 getCheckedOutDocs
583              
584             These optional arguments are supported:
585              
586             =over 4
587              
588             =item folderId
589              
590             =item maxItems
591              
592             =item skipCount
593              
594             =item orderBy
595              
596             =item filter
597              
598             =item includeRelationships
599              
600             =item renditionFilter
601              
602             =item includeAllowableActions
603              
604             =back
605              
606             =cut
607              
608             sub getCheckedOutDocs {
609             my $this = shift;
610             return $this->getCollection(CHECKED_OUT_COLL, @_)
611             }
612              
613             =item getUnfiledDocs(%params):
614              
615             returns a AtomFeed of cmis objects that
616             are currently unfiled.
617              
618             These optional arguments are supported:
619              
620             =over 4
621              
622             =item folderId
623              
624             =item maxItems
625              
626             =item skipCount
627              
628             =item orderBy
629              
630             =item filter
631              
632             =item includeRelationships
633              
634             =item renditionFilter
635              
636             =item includeAllowableActions
637              
638             =back
639              
640             =cut
641              
642             sub getUnfiledDocs {
643             my $this = shift;
644              
645             unless ($this->getCapabilities->{'Unfiling'}) {
646             throw WebService::Cmis::NotSupportedException("This repository does not support unfiling");
647             }
648              
649             return $this->getCollection(UNFILED_COLL, @_);
650             }
651              
652             =item getTypeDefinitions(%params) -> $atomFeed
653              
654             returns a AtomFeed of ObjectTypes holding
655             the base types in the repository.
656              
657             Use the normal paging options.
658              
659             =cut
660              
661             sub getTypeDefinitions {
662             my $this = shift;
663             return $this->getCollection(TYPES_COLL, @_);
664             }
665              
666             =item createEntryXmlDoc(
667             summary=>$summary
668             folder=>$parentFolder,
669             properties=>$propsList,
670             contentFile=>$filename,
671             contentData=>$data,
672             contentType=>$type
673             ) -> $atomEntry
674              
675             helper method that knows how to build an Atom entry based
676             on the properties and, optionally, the contentFile provided.
677              
678             =cut
679              
680             sub createEntryXmlDoc {
681             my $this = shift;
682             my %params = @_;
683              
684             my $xmlDoc = new XML::LibXML::Document('1.0', 'UTF-8');
685             my $entryElement = $xmlDoc->createElementNS(ATOM_NS, "entry");
686             $xmlDoc->setDocumentElement($entryElement);
687              
688             $entryElement->setNamespace(APP_NS, "app", 0);
689             $entryElement->setNamespace(CMISRA_NS, "cmisra", 0);
690             $entryElement->setNamespace(CMIS_NS, "cmis", 0);
691            
692             $entryElement->appendTextChild("summary", $params{summary}) if defined $params{summary};
693              
694             # if there is a File, encode it and add it to the XML
695             my $contentFile = $params{contentFile};
696             my $contentData = $params{contentData};
697             if (defined $contentFile || defined $contentData) {
698              
699             my $mimeType = $params{contentType};
700            
701             # read file
702             unless (defined $contentData) {
703             my $fh;
704              
705             open($fh, '<', $contentFile)
706             or throw Error::Simple("can't open file $contentFile"); # SMELL: use a custom exception
707              
708             local $/ = undef;# set to read to EOF
709             $contentData = <$fh>;
710             close($fh);
711             $contentData = '' unless $contentData; # no undefined
712             }
713              
714             # need to determine the mime type
715             unless (defined $mimeType) {
716              
717             # get the file mage used for checking
718             unless (defined $this->{fileMage}) {
719             require File::MMagic;
720             $this->{fileMage} = new File::MMagic;
721             }
722              
723             $mimeType = $this->{fileMage}->checktype_contents($contentData);
724              
725             # mimeType fallback
726             $mimeType = 'application/binary' unless defined $mimeType;
727             }
728              
729             # This used to be ATOM_NS content but there is some debate among
730             # vendors whether the ATOM_NS content must always be base64
731             # encoded. The spec does mandate that CMISRA_NS content be encoded
732             # and that element takes precedence over ATOM_NS content if it is
733             # present, so it seems reasonable to use CMIS_RA content for now
734             # and encode everything. (comments from cmislib)
735              
736             require MIME::Base64;
737             $contentData = MIME::Base64::encode_base64($contentData);
738              
739             my $contentElement = $xmlDoc->createElement('cmisra:content');
740             $contentElement->appendTextChild("cmisra:mediatype", $mimeType);
741             $contentElement->appendTextChild("cmisra:base64", $contentData);
742             $entryElement->appendChild($contentElement);
743             }
744              
745             my $objectElement = $entryElement->appendChild($xmlDoc->createElement('cmisra:object'));
746              
747             if (defined $params{properties}) {
748             my $propsElement = $objectElement->appendChild($xmlDoc->createElement('cmis:properties'));
749            
750             foreach my $property (@{$params{properties}}) {
751             #_writeCmisDebug("property=".$property->toString);
752              
753             # a name is required for most things, but not for a checkout
754             if ($property->getId eq 'cmis:name') {
755             _writeCmisDebug("got cmis:name property");
756             $entryElement->appendTextChild("title", $property->getValue);
757             }
758              
759             # create property element and add to container
760             $propsElement->appendChild($property->toXml($xmlDoc));
761             }
762             }
763              
764             # add folderId
765             $objectElement->appendTextChild('cmis:folderId', $params{folder}->getId) if defined $params{folder};
766              
767             # add repositoryId
768             $objectElement->appendTextChild('cmis:repositoryId', $this->getRepositoryId);
769              
770             #print STDERR "### created new entry:\n".$xmlDoc->toString(1)."\n###\n";
771             return $xmlDoc;
772             }
773              
774             =item createObject($parentFolder, properties => $propertyList, %params);
775              
776             creates a new CMIS Objec in the given folder using
777             the properties provided.
778              
779             To specify a custom object type, pass in a Property for
780             cmis:objectTypeId representing the type ID
781             of the instance you want to create. If you do not pass in an object
782             type ID, an instance of 'cmis:document' will be created.
783              
784             =cut
785              
786             sub createObject {
787             my $this = shift;
788             my $parentFolder = shift;
789              
790             my $postUrl;
791             if (defined $parentFolder) {
792             # get the folder represented by folderId.
793             # we'll use his 'children' link post the new child
794             $postUrl = $parentFolder->getChildrenLink;
795            
796             } else {
797             unless ($this->getCapabilities->{'Unfiling'}) {
798             throw WebService::Cmis::NotSupportedException("This repository does not support unfiling");
799             }
800              
801             # post to unfiled collection
802             $postUrl = $this->getCollectionLink(UNFILED_COLL);
803             }
804              
805             # build the Atom entry
806             my $xmlDoc = $this->createEntryXmlDoc(folder=>$parentFolder, @_);
807              
808             # post the Atom entry
809             my $result = $this->{client}->post($postUrl, $xmlDoc->toString, ATOM_XML_ENTRY_TYPE);
810              
811             # what comes back is the XML for the new document,
812             # so use it to instantiate a new document
813             # then return it
814             require WebService::Cmis::Object;
815             return new WebService::Cmis::Object(repository=>$this, xmlDoc=>$result);
816             }
817              
818             =item getTypeChildren($typeId, %params) -> $atomFeed
819              
820             returns a result set ObjectType objects corresponding to the
821             child types of the type specified by the typeId.
822              
823             If no typeId is provided, the result will be the same as calling
824             getTypeDefinitions
825              
826             See CMIS specification document 2.2.2.3 getTypeChildren
827              
828             These optional arguments are current supported:
829              
830             =over 4
831              
832             =item includePropertyDefinitions
833              
834             =item maxItems
835              
836             =item skipCount
837              
838             =back
839              
840             =cut
841              
842             sub getTypeChildren {
843             my $this = shift;
844             my $typeId = shift;
845              
846             if (defined $typeId) {
847             # if a typeId is specified, get it from the type definition's "down" link
848             my $targetType = $this->getTypeDefinition($typeId);
849             my $childrenUrl = $targetType->getLink(DOWN_REL, ATOM_XML_FEED_TYPE_P);
850              
851             #print STDERR "childrenUrl=$childrenUrl\n";
852              
853             my $result = $this->{client}->get($childrenUrl, @_);
854              
855             require WebService::Cmis::AtomFeed::ObjectTypes;
856             return new WebService::Cmis::AtomFeed::ObjectTypes(repository=>$this, xmlDoc=>$result);
857              
858             } else {
859              
860             # otherwise, if a typeId is not specified, return
861             # the list of base types
862             return $this->getTypeDefinitions;
863             }
864             }
865              
866             =item getTypeDescendants($typeId, %params) -> $atomFeed
867              
868             Returns a result set ObjectType objects corresponding to the
869             descendant types of the type specified by the typeId.
870              
871             If no typeId is provided, the repository's "typesdescendants" URL
872             will be called to determine the list of descendant types.
873              
874             See CMIS specification document 2.2.2.4 getTypeDescendants
875              
876             These optional arguments are supported:
877              
878             =over 4
879              
880             =item depth
881              
882             =item includePropertyDefinitions
883              
884             =back
885              
886             =cut
887              
888             sub getTypeDescendants {
889             my $this = shift;
890             my $typeId = shift;
891            
892             my $descendUrl;
893             if (defined $typeId) {
894             # if a typeId is specified, get it from the type definition's, "down" link
895             my $targetType = $this->getTypeDefinition($typeId);
896             $descendUrl = $targetType->getLink(DOWN_REL, CMIS_TREE_TYPE_P);
897             } else {
898             $descendUrl = $this->getLink(TYPE_DESCENDANTS_REL);
899             }
900              
901             #print STDERR "descendUrl=$descendUrl\n";
902              
903             unless (defined $descendUrl) {
904             throw Error::Simple("Could not determine the type descendants URL"); # SMELL: do a custom exception
905             }
906              
907             my $result = $this->{client}->get($descendUrl, @_);
908              
909             require WebService::Cmis::AtomFeed::ObjectTypes;
910             return new WebService::Cmis::AtomFeed::ObjectTypes(repository=>$this, xmlDoc=>$result);
911             }
912              
913             =item query(statement, %params):
914              
915             Returns a result set of CMIS Objects based on the CMIS
916             Query Language passed in as the statement. The actual objects
917             returned will be instances of the appropriate child class based
918             on the object's base type ID.
919              
920             In order for the results to be properly instantiated as objects,
921             make sure you include 'cmis:objectId' as one of the fields in
922             your select statement, or just use "SELECT \*".
923              
924             If you want the search results to automatically be instantiated with
925             the appropriate sub-class of CMIS Object you must either
926             include cmis:baseTypeId as one of the fields in your select statement
927             or just use "SELECT \*".
928              
929             See CMIS specification document 2.2.6.1 query
930              
931             The following optional arguments are supported:
932              
933             =over 4
934              
935             =item searchAllVersions
936              
937             =item includeRelationships
938              
939             =item renditionFilter
940              
941             =item includeAllowableActions
942              
943             =item maxItems
944              
945             =item skipCount
946              
947             =back
948              
949             =cut
950              
951             sub query {
952             my $this = shift;
953             my $statement = shift;
954              
955             # get the URL this repository uses to accept query POSTs
956             my $queryUrl = $this->getCollectionLink(QUERY_COLL);
957              
958             # build the CMIS query XML that we're going to POST
959             my $xmlDoc = $this->_getQueryXmlDoc($statement, @_);
960              
961             # do the POST
962             my $result = $this->{client}->post($queryUrl, $xmlDoc->toString, CMIS_QUERY_TYPE);
963              
964             # return the result set
965             require WebService::Cmis::AtomFeed::Objects;
966             return new WebService::Cmis::AtomFeed::Objects(repository=>$this, xmlDoc=>$result);
967             }
968              
969             # Utility method that knows how to build CMIS query xml around the specified query statement.
970             sub _getQueryXmlDoc {
971             my $this = shift;
972             my $statement = shift;
973             my %params = @_;
974              
975             my $xmlDoc = new XML::LibXML::Document('1.0', 'UTF-8');
976              
977             my $queryElement = $xmlDoc->createElementNS(CMIS_NS, "query");
978              
979             $xmlDoc->setDocumentElement($queryElement);
980              
981             my $statementElement = $xmlDoc->createElementNS(CMIS_NS, "statement");
982             $statementElement->addChild($xmlDoc->createCDATASection($statement));
983             $queryElement->appendChild($statementElement);
984              
985             foreach my $key (keys %params) {
986             my $optionElement = $xmlDoc->createElementNS(CMIS_NS, $key);
987             $optionElement->appendText($params{$key});
988             $queryElement->appendChild($optionElement);
989             }
990              
991             #_writeCmisDebug("query:\n".$xmlDoc->toString(1));
992              
993             return $xmlDoc;
994             }
995              
996             =item getLatestChangeLogToken () -> $token
997              
998             returns a token to ge use fetching a changes atom feed.
999              
1000             =cut
1001              
1002             sub getLatestChangeLogToken {
1003             my $this = shift;
1004              
1005             unless ($this->getCapabilities()->{'Changes'}) {
1006             throw WebService::Cmis::NotSupportedException("This repository does not support change logs");
1007             }
1008              
1009             return $$this->getRepositoryInfo->{latestChangeLogToken};
1010             }
1011              
1012             =item getContentChanges(%params) -> $atomFeed
1013              
1014             returns a AtomFeed containing ChangeEntry objects.
1015              
1016             See CMIS specification document 2.2.6.2 getContentChanges
1017              
1018             The following optional arguments are supported:
1019              
1020             =over 4
1021              
1022             =item changeLogToken
1023              
1024             =item includeProperties
1025              
1026             =item includePolicyIDs
1027              
1028             =item includeACL
1029              
1030             =item maxItems
1031              
1032             =back
1033              
1034             You can get the latest change log token by inspecting the repository
1035             info via Repository.getRepositoryInfo.
1036              
1037             =cut
1038              
1039             sub getContentChanges {
1040             my $this = shift;
1041             my %params = @_;
1042            
1043             #$params{changeLogToken} = $this->getLatestChangeLogToken() unless defined $params{changeLogToken};
1044              
1045             unless ($this->getCapabilities()->{'Changes'}) {
1046             throw WebService::Cmis::NotSupportedException("This repository does not support change logs");
1047             }
1048              
1049             my $changesUrl = $this->getLink(CHANGE_LOG_REL);
1050             my $result = $this->{client}->get($changesUrl, %params);
1051              
1052             # return the result set
1053             require WebService::Cmis::AtomFeed::ChangeEntries;
1054             return new WebService::Cmis::AtomFeed::ChangeEntries(repository=>$this, xmlDoc=>$result);
1055             }
1056              
1057             =item createDocument(
1058             $name,
1059             folder=>$parentFolder,
1060             properties=>$propsList,
1061             contentFile=>$filename,
1062             contentData=>$data,
1063             contentType=>$type,
1064             ) -> $cmisDocument
1065              
1066             creates a new Document object in the parent folder provided or filed to the
1067             Unfiled collection of the repository.
1068              
1069             The method will attempt to guess the appropriate content type and encoding
1070             based on the file. To specify it yourself, pass them in via the contentType and
1071              
1072             To specify a custom object type, pass in a Property for cmis:objectTypeId
1073             representing the type ID of the instance you want to create. If you do not pass
1074             in an object type ID, an instance of 'cmis:document' will be created.
1075              
1076             See CMIS specification document 2.2.4.1 createDument
1077              
1078             =cut
1079              
1080             sub createDocument {
1081             my $this = shift;
1082             my $name = shift;
1083             my %params = @_;
1084              
1085             my $parentFolder = delete $params{folder};
1086             my $properties = delete $params{properties};
1087             $properties = [] unless defined $properties;
1088              
1089             # construct properties
1090             require WebService::Cmis::Property;
1091              
1092             push @$properties, WebService::Cmis::Property::newString(
1093             id => 'cmis:name',
1094             value => $name,
1095             );
1096              
1097             my $foundObjectTypeId = 0;
1098             foreach my $prop (@$properties) {
1099             if ($prop->getId eq 'cmis:objectTypeId') {
1100             $foundObjectTypeId = 1;
1101             last;
1102             }
1103             }
1104              
1105             unless ($foundObjectTypeId) {
1106             push @$properties, WebService::Cmis::Property::newId(
1107             id => 'cmis:objectTypeId',
1108             value => 'cmis:document',
1109             );
1110             }
1111              
1112             # create the object
1113             return $this->createObject(
1114             $parentFolder,
1115             properties=>$properties,
1116             %params,
1117             );
1118             }
1119              
1120              
1121             =item createFolder($name, folder=>$parentFolder, properties=>$propertyList, %params) -> $cmisFolder
1122              
1123             creates a new CMIS Folder using the properties provided.
1124              
1125             To specify a custom folder type, pass in a property called
1126             cmis:objectTypeId representing the type ID
1127             of the instance you want to create. If you do not pass in an object
1128             type ID, an instance of 'cmis:folder' will be created.
1129              
1130             my $rootFolder = $repo->getRootFolder;
1131              
1132             my $subFolder = $rootFolder->createFolder(
1133             'My new folder',
1134             summary => "This is my new test folder."
1135             );
1136              
1137             my $repo = $repo->createFolder(
1138             'My other folder',
1139             folder => $rootFolder,
1140             summary => "This is my other test folder."
1141             );
1142              
1143             See CMIS specification document 2.2.4.3 createFolder
1144              
1145             =cut
1146              
1147             sub createFolder {
1148             my $this = shift;
1149             my $name = shift;
1150             my %params = @_;
1151              
1152             my $parentFolder = delete $params{folder};
1153              
1154             my $properties = delete $params{properties};
1155             $properties = [] unless defined $properties;
1156              
1157             # construct properties
1158             require WebService::Cmis::Property;
1159              
1160             push @$properties,
1161             WebService::Cmis::Property::newString(
1162             id => 'cmis:name',
1163             value => $name,
1164             );
1165              
1166             my $foundObjectTypeId = 0;
1167             foreach my $prop (@$properties) {
1168             if ($prop->getId eq 'cmis:objectTypeId') {
1169             $foundObjectTypeId = 1;
1170             last;
1171             }
1172             }
1173              
1174             unless ($foundObjectTypeId) {
1175             push @$properties,
1176             WebService::Cmis::Property::newId(
1177             id => 'cmis:objectTypeId',
1178             value => 'cmis:folder',
1179             );
1180             }
1181              
1182             # create the object
1183             return $this->createObject(
1184             $parentFolder,
1185             properties => $properties,
1186             %params,
1187             );
1188             }
1189              
1190             =item createRelationship
1191              
1192             TODO: This is not yet implemented.
1193              
1194             =cut
1195              
1196             sub createRelationship { throw WebService::Cmis::NotImplementedException; }
1197              
1198             =item createPolicy
1199              
1200             TODO: This is not yet implemented.
1201              
1202             =cut
1203              
1204             sub createPolicy { throw WebService::Cmis::NotImplementedException; }
1205              
1206             =back
1207              
1208             =head1 AUTHOR
1209              
1210             Michael Daum C<< >>
1211              
1212             =head1 COPYRIGHT AND LICENSE
1213              
1214             Copyright 2012-2013 Michael Daum
1215              
1216             This module is free software; you can redistribute it and/or modify it under
1217             the same terms as Perl itself. See F.
1218              
1219             =cut
1220              
1221              
1222             1;
1223