File Coverage

blib/lib/WebService/Cmis/Object.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::Object;
2              
3             =head1 NAME
4              
5             WebService::Cmis::Object - Representation of a cmis object
6              
7             =head1 DESCRIPTION
8              
9             This class provides the bulk of methods to work with CMIS objects.
10             When creating a new object on the base of an xml document, will
11             it be subclassed correctly reading the C property.
12              
13             my $obj = WebService::Cmis::Object(
14             repository=>$this->{repository},
15             xmlDoc=>$xmlDoc
16             );
17              
18             if ($obj->isa('WebService::Cmis::Folder')) {
19             # this is a folder
20             }
21              
22             Parent class: L
23              
24             Sub classes: L, L,
25             L, L.
26              
27             =cut
28              
29 1     1   5 use strict;
  1         1  
  1         33  
30 1     1   5 use warnings;
  1         2  
  1         36  
31 1     1   5 use WebService::Cmis qw(:namespaces :relations :contenttypes :collections :utils);
  1         3  
  1         411  
32 1     1   414 use XML::LibXML qw(:libxml);
  0            
  0            
33             use WebService::Cmis::NotImplementedException;
34             use Error qw(:try);
35             use URI ();
36             use WebService::Cmis::AtomEntry ();
37              
38             our @ISA = qw(WebService::Cmis::AtomEntry);
39              
40             our %classOfBaseTypeId = (
41             'cmis:folder' => 'WebService::Cmis::Folder',
42             'cmis:document' => 'WebService::Cmis::Document',
43             'cmis:relationship' => 'WebService::Cmis::Relationship',
44             'cmis:policy' => 'WebService::Cmis::Policy',
45             );
46              
47             our $CMIS_XPATH_PROPERTIES = new XML::LibXML::XPathExpression('./*[local-name()="object" and namespace-uri()="'.CMISRA_NS.'"]/*[local-name()="properties" and namespace-uri()="'.CMIS_NS.'"]//*[@propertyDefinitionId]');
48             our $CMIS_XPATH_ALLOWABLEACTIONS = new XML::LibXML::XPathExpression('//cmis:allowableActions');
49             our $CMIS_XPATH_ACL = new XML::LibXML::XPathExpression('//cmis:acl');
50              
51             =head1 METHODS
52              
53             =over 4
54              
55             =item new(repository=>$repository, xmlDoc=>$xmlDoc) -> $object
56              
57             constructor to get a specialized object, a subclass of WebService::Cmis::Object
58             representing a cmis:document, cmis:folder, cmis:relationship or cmis:policy.
59              
60             =cut
61              
62             sub new {
63             my $class = shift;
64              
65             my $obj = $class->SUPER::new(@_);
66              
67             my $baseTypeId = $obj->getProperty("cmis:baseTypeId");
68             return $obj unless $baseTypeId;
69              
70             my $subClass = $classOfBaseTypeId{$baseTypeId};
71             return $obj unless $subClass;
72              
73             eval "use $subClass";
74             if ($@) {
75             throw Error::Simple($@);
76             }
77              
78             return bless($obj, $subClass);
79             }
80              
81             # resets the internal cache of this entry.
82             sub _initData {
83             my $this = shift;
84              
85             $this->SUPER::_initData;
86              
87             $this->{properties} = undef;
88             $this->{allowableActions} = undef;
89             $this->{acl} = undef;
90             }
91              
92             =item DESTROY
93              
94             clean up internal caches
95              
96             =cut
97              
98             sub DESTROY {
99             my $this = shift;
100              
101             #print STDERR "called Object::DESTROY\n";
102              
103             $this->_initData;
104              
105             $this->{xmldoc} = undef;
106             $this->{repository} = undef;
107             }
108              
109             =item reload(%params)
110              
111             Fetches the latest representation of this object from the CMIS service.
112             Some methods, like document->checkout do this for you.
113              
114             If you call reload with a properties filter, the filter will be in
115             effect on subsequent calls until the filter argument is changed. To
116             reset to the full list of properties, call reload with filter set to
117             '*'.
118              
119             Parameters:
120              
121             =over 4
122              
123             =item * returnVersion
124              
125             =item * filter
126              
127             =item * includeAllowableActions
128              
129             =item * includePolicyIds
130              
131             =item * includeRelationships
132              
133             =item * includeACL
134              
135             =item * renditionFilter
136              
137             =back
138              
139             =cut
140              
141             sub reload {
142             my ($this, %params) = @_;
143              
144             throw Error::Simple("can't reload Object without an id or xmlDoc") unless defined $this->{id} || defined $this->{xmlDoc};
145              
146             #print STDERR "reload this:\n".join("\n", map(" ".$_."=".($this->{$_}||'undef'), keys %$this))."");
147              
148             my $byObjectIdUrl = $this->{repository}->getUriTemplate('objectbyid');
149              
150             require WebService::Cmis::Property::Boolean;
151              
152             my $id = $params{id} || $this->{id} || $this->getId();
153              
154             $byObjectIdUrl =~ s/{id}/_urlEncode($id)/ge;
155             $byObjectIdUrl =~ s/{filter}/_urlEncode($params{filter}||'')/ge;
156             $byObjectIdUrl =~ s/{includeAllowableActions}/WebService::Cmis::Property::Boolean->unparse($params{includeAllowableActions}||'false')/ge;
157             $byObjectIdUrl =~ s/{includePolicyIds}/WebService::Cmis::Property::Boolean->unparse($params{includePolicyIds}||'false')/ge;
158             $byObjectIdUrl =~ s/{includeRelationships}/WebService::Cmis::Property::Boolean->unparse($params{includeRelationships}||'')/ge;
159             $byObjectIdUrl =~ s/{includeACL}/WebService::Cmis::Property::Boolean->unparse($params{includeACL}||'false')/ge;
160             $byObjectIdUrl =~ s/{renditionFilter}/_urlEncode($params{renditionFilter}||'')/ge;
161              
162             # SMELL: returnVersion not covered by uri template
163             my %extraParams = %{$this->{extra_params}||{}};
164             $extraParams{returnVersion} = $params{returnVersion} if defined $params{returnVersion};
165              
166             # auto clear cache
167             #$this->{repository}{client}->removeFromCache($byObjectIdUrl, %extraParams);
168            
169             $this->{xmlDoc} = $this->{repository}{client}->get($byObjectIdUrl, %extraParams);
170             $this->{id} = undef; # consume {id} only valid during object creation
171             $this->_initData;
172             }
173              
174             =item getId() -> $id
175              
176             returns the object ID for this object.
177              
178             =cut
179              
180             sub getId {
181             return $_[0]->getProperty("cmis:objectId");
182             }
183              
184             =item getName() -> $name
185              
186             returns the cmis:name property.
187              
188             =cut
189              
190             sub getName {
191             return $_[0]->getProperty("cmis:name");
192             }
193              
194              
195             =item getPath() -> $path
196              
197             returns the cmis:path property.
198              
199             =cut
200              
201             sub getPath {
202             return $_[0]->getProperty("cmis:path");
203             }
204              
205             =item getTypeId() -> $typeId
206              
207             returns the cmis:objectTypeId property.
208              
209             =cut
210              
211             sub getTypeId {
212             return $_[0]->getProperty("cmis:objectTypeId");
213             }
214              
215             =item getProperties($filter) -> %properties;
216              
217             returns a hash of the object's L. If
218             CMIS returns an empty element for a property, the property will be in the hash
219             with an undef value
220              
221             See CMIS specification document 2.2.4.8 getProperties
222              
223             =cut
224              
225             sub getProperties {
226             my ($this, $filter) = @_;
227              
228             unless (defined $this->{properties}) {
229             require WebService::Cmis::Property;
230             my $doc = $this->_getDocumentElement;
231             foreach my $propNode ($doc->findnodes($CMIS_XPATH_PROPERTIES)) {
232             my $property = WebService::Cmis::Property::load($propNode);
233             my $propId = $property->getId;
234             #print STDERR "property = ".$property->toString."\n";
235             if (defined $this->{properties}{$propId}) {
236             die "duplicate property $propId in ".$doc->toString(1);
237             }
238             $this->{properties}{$propId} = $property;
239             }
240             }
241              
242             return $this->{properties} if !defined($filter) || $filter eq '*';
243              
244             my $filterPattern;
245             if (defined $filter && $filter ne '*') {
246             $filterPattern = '^('.join('|', map {(($_ =~ /^.+:.+$/)? $_: 'cmis:'.$_)} split(/\s*,\s*/, $filter)).')$';
247             #print STDERR "filterPattern=$filterPattern\n";
248             }
249              
250             my %filteredProps = map {$_ => $this->{properties}{$_}} grep {/$filterPattern/} keys %{$this->{properties}};
251             return \%filteredProps;
252             }
253              
254             =item getProperty($propName) -> $propValue
255              
256             returns the value of a given property or undef if not available.
257              
258             This is not covered by the cmis specs but makes live easier.
259              
260             =cut
261              
262             sub getProperty {
263             my ($this, $propName) = @_;
264              
265             my $props = $this->getProperties;
266             return unless $props->{$propName};
267             return $props->{$propName}->getValue;
268             }
269              
270             =item getAllowableActions() -> %allowableActions
271              
272             returns a hash of allowable actions, keyed off of the action name.
273              
274             my $allowableActions = $obj->getAllowableActions;
275             while (my ($action, $booleanFlag) = each %$allowableActions) {
276             print "$action=$booleanFlag\n";
277             }
278              
279             See CMIS specification document 2.2.4.6 getAllowableActions
280              
281             =cut
282              
283             sub getAllowableActions {
284             my $this = shift;
285              
286             unless (defined $this->{allowableActions}) {
287             my $node;
288              
289             if ($this->{xmlDoc}->exists($CMIS_XPATH_ALLOWABLEACTIONS)) {
290             _writeCmisDebug("getting allowable actions from doc");
291              
292             ($node) = $this->{xmlDoc}->findnodes($CMIS_XPATH_ALLOWABLEACTIONS);
293              
294             } else {
295             my $url = $this->getLink(ALLOWABLEACTIONS_REL);
296             my $result = $this->{repository}{client}->get($url);
297             $node = $result->getDocumentElement;
298             }
299              
300             #print STDERR "getAllowableActions: result=".$node->toString(2)."\n";
301              
302             require WebService::Cmis::Property::Boolean;
303              
304             foreach my $node ($node->childNodes) {
305             #print STDERR "node=".$node->toString(1)."\n";
306             next unless $node->nodeType == XML_ELEMENT_NODE;
307             $this->{allowableActions}{$node->localname} = WebService::Cmis::Property::Boolean->parse($node->string_value);
308             }
309             }
310              
311             return $this->{allowableActions};
312             }
313              
314             =item getACL() -> $acl
315              
316             returns the L for this object.
317              
318             The repository must have ACL capabilities 'manage' or 'discover'.
319              
320             The optional C argument is currently not supported.
321              
322             See CMIS specification document 2.2.10.1 getACL
323              
324             =cut
325              
326             sub getACL {
327             my $this = shift;
328              
329             unless (defined $this->{acl}) {
330              
331             unless ($this->{repository}->getCapabilities()->{'ACL'} =~ /^(manage|discover)$/) {
332             throw WebService::Cmis::NotSupportedException("This repository does not allow to manage ACLs");
333             }
334              
335             require WebService::Cmis::ACL;
336              
337             my $node;
338              
339             if ($this->{xmlDoc}->exists($CMIS_XPATH_ACL)) {
340             _writeCmisDebug("getting acl from doc");
341             ($node) = $this->{xmlDoc}->findnodes($CMIS_XPATH_ACL);
342             } else {
343             my $url = $this->getLink(ACL_REL);
344             my $result = $this->{repository}{client}->get($url);
345             $node = $result->getDocumentElement;
346             }
347              
348             $this->{acl} = new WebService::Cmis::ACL(xmlDoc=>$node);
349             }
350              
351             return $this->{acl};
352             }
353              
354             =item getSelfLink -> $href
355              
356             returns the URL used to retrieve this object.
357              
358             =cut
359              
360             sub getSelfLink {
361             return $_[0]->getLink(SELF_REL);
362             }
363              
364             =item getEditLink -> $href
365              
366             returns the URL that can be used with the HTTP PUT method to modify the
367             atom:entry for the CMIS resource
368              
369             See CMIS specification document 3.4.3.1 Existing Link Relations
370              
371             =cut
372              
373             sub getEditLink {
374             return $_[0]->getLink(EDIT_MEDIA_REL);
375             }
376              
377             =item getAppliedPolicies(%params) -> $atomFeed
378              
379             returns the L applied to
380             this object.
381              
382             See CMIS specification document 2.2.9.3 getAppliedPolicies
383              
384             =cut
385              
386             sub getAppliedPolicies {
387             my $this = shift;
388              
389              
390             # depends on this object's canGetAppliedPolicies allowable action
391             unless ($this->getAllowableActions->{'canGetAppliedPolicies'}) {
392             throw WebService::Cmis::NotSupportedException('This object has canGetAppliedPolicies set to false');
393             }
394              
395             require WebService::Cmis::AtomFeed::Objects;
396              
397             my $url = $this->getLink(POLICIES_REL, @_);
398             my $result = $this->{repository}{client}->get($url, @_);
399              
400             return new WebService::Cmis::AtomFeed::Objects(repository=>$this->{repository}, xmlDoc=>$result);
401             }
402              
403             =item getObjectParents(%params) -> $atomFeedOrEntry
404              
405             gets the parent(s) for the specified non-folder, fileable object.
406             This is either an L or
407             the parent L depending on the "up" relation.
408              
409             The following optional arguments are - NOT YET - supported: (TODO)
410              
411             =over 4
412              
413             =item * filter
414              
415             =item * includeRelationships
416              
417             =item * renditionFilter
418              
419             =item * includeAllowableActions
420              
421             =item * includeRelativePathSegment
422              
423             =back
424              
425             See CMIS specification document 2.2.3.5 getObjectParents
426              
427             =cut
428              
429             sub getObjectParents {
430             my $this = shift;
431             my %params = @_;
432              
433             # get the appropriate 'up' link
434             my $parentUrl = $this->getLink(UP_REL);
435              
436             unless ($parentUrl) {
437             throw WebService::Cmis::NotSupportedException('object does not support getObjectParents');
438             }
439             # invoke the URL
440             my $result = $this->{repository}{client}->get($parentUrl, @_);
441              
442             #print STDERR "getObjectParents=".$result->toString(1)."\n";
443              
444             if ($result->documentElement->localName eq 'feed') {
445             # return the result set
446             require WebService::Cmis::AtomFeed::Objects;
447             return new WebService::Cmis::AtomFeed::Objects(repository=>$this->{repository}, xmlDoc=>$result);
448             } else {
449             # return the result set
450             return new WebService::Cmis::Object(repository=>$this->{repository}, xmlDoc=>$result);
451             }
452             }
453              
454             =item getRelationships(%params) -> $atomFeed
455              
456             returns a result L
457             objects|WebService::Cmis::AtomFeed::Objects> for each relationship where the
458             source is this object.
459              
460             The following optional arguments are - NOT YET - supported: (TODO)
461              
462             =over 4
463              
464             =item * includeSubRelationshipTypes
465              
466             =item * relationshipDirection
467              
468             =item * typeId
469              
470             =item * maxItems
471              
472             =item * skipCount
473              
474             =item * filter
475              
476             =item * includeAllowableActions
477              
478             =back
479              
480             See CMIS specification document 2.2.8.1 getObjectRelationships
481              
482             =cut
483              
484             sub getRelationships {
485             my $this = shift;
486             my %params = @_;
487              
488             require WebService::Cmis::AtomFeed::Objects;
489              
490             my $url = $this->getLink(RELATIONSHIPS_REL);
491              
492             unless ($url) {
493             throw Error::Simple('could not determine relationships URL');
494             }
495              
496             my $result = $this->{repository}{client}->get($url, @_);
497             return new WebService::Cmis::AtomFeed::Objects(repository => $this->{repository}, xmlDoc => $result);
498             }
499              
500             =item delete(%params)
501              
502             Deletes this cmis object from the repository. Note that in the case of a Folder
503             object, some repositories will refuse to delete it if it contains children and
504             some will delete it without complaint. If what you really want to do is delete
505             the folder and all of its descendants, use L instead.
506              
507             =over 4
508              
509             =item * allVersions: if TRUE (default), then delete all versions of the
510             document. If FALSE, delete only the document object specified. The Repository
511             MUST ignore the value of this parameter when this service is invoke on a
512             non-document object or non-versionable document object.
513              
514             =back
515              
516             See CMIS specification document 2.2.4.14 delete
517              
518             =cut
519              
520             sub delete {
521             my $this = shift;
522             my %params = @_;
523              
524             my $url = $this->getSelfLink;
525             return $this->{repository}{client}->delete($url, @_);
526             }
527              
528             =item move($sourceFolder, $targetFolder) -> $this
529              
530             Moves the specified file-able object from one folder to another.
531              
532             See CMIS specification document 2.2.4.13 move
533              
534             =cut
535              
536             sub move {
537             my ($this, $sourceFolder, $targetFolder) = @_;
538              
539             return $this->moveTo($targetFolder) unless defined $sourceFolder;
540              
541             my $targetUrl = $targetFolder->getLink(DOWN_REL, ATOM_XML_FEED_TYPE_P, 1);
542              
543             my $uri = new URI($targetUrl);
544             my %queryParams = ($uri->query_form, sourceFolderId=>$sourceFolder->getId);
545             $uri->query_form(%queryParams);
546             $targetUrl = $uri->as_string;
547              
548             # post it to to the checkedout collection URL
549             my $result = $this->{repository}{client}->post($targetUrl, $this->_xmlDoc->toString, ATOM_XML_ENTRY_TYPE);
550              
551             # now that the doc is moved, we need to refresh the XML
552             # to pick up the prop updates related to the move
553             $this->{xmlDoc} = $result;
554             $this->_initData;
555              
556             #return new WebService::Cmis::Object(repository=>$this->{repository}, xmlDoc=>$result);
557             return $this;
558             }
559              
560             =item moveTo($targetFolder) -> $this
561              
562             Convenience function to move an object from its parent folder to a given target folder.
563             Same as Folder::addObject but in reverse logic
564              
565             =cut
566              
567             sub moveTo {
568             my ($this, $targetFolder) = @_;
569              
570             my $parents = $this->getObjectParents;
571             my $parentFolder;
572              
573             if ($parents->isa("WebService::Cmis::AtomFeed")) {
574             $parentFolder = $parents->getNext; #SMELL: what if there are multiple parents
575             } else {
576             $parentFolder = $parents;
577             }
578              
579             return $this->move($parentFolder, $targetFolder);
580             }
581              
582             =item unfile($folder)
583              
584             removes this object from the given parent folder.
585             If the $folder parameter is not provided, the document is removed from any of its parent folders.
586              
587             See CMIS specification document 2.2.5.2
588              
589             =cut
590              
591             sub unfile {
592             my $this = shift;
593             my $folder = shift;
594              
595             unless ($this->{repository}->getCapabilities()->{'Unfiling'}) {
596             throw WebService::Cmis::NotSupportedException("This repository does not support unfiling");
597             }
598              
599             my $unfiledLink = $this->{repository}->getCollectionLink(UNFILED_COLL, ATOM_XML_FEED_TYPE_P);
600              
601             if ($folder) {
602             my $uri = new URI($unfiledLink);
603             my %queryParams = ($uri->query_form, folderId=>$folder->getId);
604             $uri->query_form(%queryParams);
605             $unfiledLink = $uri->as_string;
606             }
607              
608             # post it to to the unfiled collection URL
609             my $result = $this->{repository}{client}->post($unfiledLink, $this->_xmlDoc->toString, ATOM_XML_ENTRY_TYPE);
610              
611             # now that the doc is moved, we need to refresh the XML
612             # to pick up the prop updates related to the move
613             $this->reload;
614              
615             #return new WebService::Cmis::Object(repository=>$this->{repository}, xmlDoc=>$result);
616             return $this;
617             }
618              
619             =item updateProperties($propertyList) -> $this
620              
621             TODO: The optional changeToken is not yet supported.
622              
623             Updates the properties of an object with the properties provided.
624             Only provide the set of properties that need to be updated.
625              
626              
627             $folder = $repo->getObjectByPath('/SomeFolder');
628             $folder->getName; # returns SomeFolder
629              
630             $folder->updateProperties([
631             WebService::Cmis::Property::newString(
632             id => 'cmis:name',
633             value => 'SomeOtherName',
634             ),
635             ]);
636              
637             $folder->getName; # returns SomeOtherName
638              
639             See CMIS specification document 2.2.4.12 updateProperties
640              
641             =cut
642              
643             sub updateProperties {
644             my $this = shift;
645              
646             # get the self link
647             my $selfUrl = $this->getSelfLink;
648              
649             # build the entry based on the properties provided
650             my $xmlEntryDoc = $this->{repository}->createEntryXmlDoc(properties => (@_));
651              
652             # do a PUT of the entry
653             my $result = $this->{repository}{client}->put($selfUrl, $xmlEntryDoc->toString, ATOM_XML_TYPE);
654              
655             # reset the xmlDoc for this object with what we got back from
656             # the PUT, then call initData we dont' want to call
657             # self.reload because we've already got the parsed XML--
658             # there's no need to fetch it again
659              
660             $this->{xmlDoc} = $result;
661             $this->_initData;
662              
663             return $this;
664             }
665              
666             =item getSummary -> $summary
667              
668             overrides AtomEntry::getSummary
669              
670             =cut
671              
672             sub getSummary {
673             my $this = shift;
674              
675             my $summary;
676              
677             $summary = $this->getProperty("cm:description"); # since alfresco 4
678             $summary = $this->getProperty("dc:description") unless defined $summary; # dublin core
679             $summary = $this->SUPER::getSummary() unless defined $summary; # good ol' atom
680              
681             return $summary;
682             }
683              
684             =item updateSummary
685              
686             overrides AtomEntry::updateSummary
687              
688             =cut
689              
690             sub updateSummary {
691             my ($this, $text) = @_;
692              
693             my $vendorName = $this->{repository}->getRepositoryInfo->{vendorName};
694              
695             # if ($vendorName =~ /alfresco/i) {
696             # return $this->updateProperties([
697             # WebService::Cmis::Property::newString(
698             # id => 'cm:description',
699             # value => $text,
700             # ),
701             # ]);
702             # }
703              
704             # vendors using dublin core
705             if ($vendorName =~ /nuxeo/i) {
706             return $this->updateProperties([
707             WebService::Cmis::Property::newString(
708             id => 'dc:description',
709             value => $text,
710             ),
711             ]);
712             }
713              
714             # fallback to atom:summary. some vendors sync this into their model properly.
715             return $this->SUPER::updateSummary($text);
716             }
717              
718              
719             =item rename($string) -> $this
720              
721             rename this object updating its cmis:properties
722              
723             =cut
724              
725             sub rename {
726             return $_[0]->updateProperties([
727             WebService::Cmis::Property::newString(
728             id => 'cmis:name',
729             value => $_[1],
730             ),
731             ]);
732             }
733              
734             =item applyACL($acl) -> $acl
735              
736             applies specified L to the object and returns
737             the updated ACLs as stored on the server.
738              
739             my $obj = $repo->getObject($id);
740             my $acl = $obj->getACL->addEntry(
741             new WebService::Cmis::ACE(
742             principalId => 'jdoe',
743             permissions => ['cmis:write', 'cmis:read'],
744             direct => 'true',
745             )
746             );
747             my $updatedACL => $obj->applyACL($acl);
748              
749             See CMIS specification document 2.2.10.2 applyACL
750              
751             =cut
752              
753             sub applyACL {
754             my ($this, $acl) = @_;
755              
756             unless ($this->{repository}->getCapabilities()->{'ACL'} eq 'manage') {
757             throw WebService::Cmis::NotSupportedException("This repository does not allow to manage ACLs");
758             }
759              
760             my $url = $this->getLink(ACL_REL);
761             unless ($url) {
762             throw Error::Simple("Could not determine the object's ACL URL");
763             }
764              
765             my $xmlDoc = $acl->getXmlDoc;
766             my $result = $this->{repository}{client}->put($url, $xmlDoc->toString(2), CMIS_ACL_TYPE);
767             #print STDERR "result=".$result->toString(1)."\n";
768              
769             $this->{acl} = new WebService::Cmis::ACL(xmlDoc=>$result);
770              
771             return $this->{acl};
772             }
773              
774             =item applyPolicy()
775              
776             TODO: This is not yet implemented.
777              
778             =cut
779              
780             sub applyPolicy { throw WebService::Cmis::NotImplementedException; }
781              
782             =item createRelationship()
783              
784             TODO: This is not yet implemented.
785              
786             =cut
787              
788             sub createRelationship { throw WebService::Cmis::NotImplementedException; }
789              
790             =item removePolicy()
791              
792             TODO: This is not yet implemented.
793              
794             =cut
795              
796             sub removePolicy { throw WebService::Cmis::NotImplementedException; }
797              
798             =back
799              
800             =head1 COPYRIGHT AND LICENSE
801              
802             Copyright 2012-2013 Michael Daum
803              
804             This module is free software; you can redistribute it and/or modify it under
805             the same terms as Perl itself. See F.
806              
807             =cut
808              
809             1;