line
stmt
bran
cond
sub
pod
time
code
1
package WebService::Cmis::Document;
2
3
=head1 NAME
4
5
WebService::Cmis::Document - Representation of a cmis document
6
7
=head1 DESCRIPTION
8
9
Document objects are the elementary information entities managed by the repository.
10
This class represents a document object as returned by a L.
11
12
See CMIS specification document 2.1.4 Document Object
13
14
Parent class: L
15
16
=cut
17
18
1
1
43816
use strict;
1
4
1
51
19
1
1
9
use warnings;
1
2
1
49
20
1
1
6
use WebService::Cmis qw(:collections :contenttypes :namespaces :relations :utils);
1
3
1
442
21
1
1
103
use WebService::Cmis::Object ();
0
0
22
use WebService::Cmis::NotImplementedException ();
23
use WebService::Cmis::NotSupportedException ();
24
use XML::LibXML qw(:libxml);
25
use Error qw(:try);
26
our @ISA = ('WebService::Cmis::Object');
27
28
our $CMIS_XPATH_CONTENT_LINK = new XML::LibXML::XPathExpression('//*[local-name() = "content" and namespace-uri() = "'.ATOM_NS.'"]/@src');
29
our $CMIS_XPATH_RENDITIONS = new XML::LibXML::XPathExpression('//*[local-name()="object" and namespace-uri()="'.CMISRA_NS.'"]/*[local-name()="rendition" and namespace-uri()="'.CMIS_NS.'"]');
30
31
=head1 METHODS
32
33
=over 4
34
35
=cut
36
37
# clear document-specific caches
38
sub _initData {
39
my $this = shift;
40
41
$this->SUPER::_initData();
42
undef $this->{renditions};
43
}
44
45
=item checkOut() -> $pwc
46
47
performs a checkOut on this document and returns the
48
Private Working Copy (PWC), which is also an instance of
49
Document
50
51
See CMIS specification document 2.2.7.1 checkOut
52
53
=cut
54
55
sub checkOut {
56
my $this = shift;
57
58
require WebService::Cmis::Property;
59
60
# get the checkedout collection URL
61
my $checkoutUrl = $this->{repository}->getCollectionLink(CHECKED_OUT_COLL);
62
throw Error::Simple("Could not determine the checkedout collection url.") unless defined $checkoutUrl;
63
64
# get this document's object ID
65
# build entry XML with it
66
my $entryXmlDoc = $this->{repository}->createEntryXmlDoc(
67
properties => [
68
WebService::Cmis::Property::newId(
69
id=>"cmis:objectId",
70
queryName=>"cmis:objectId",
71
value=>$this->getId
72
)
73
]
74
);
75
76
#print STDERR "entryXmlDoc=".$entryXmlDoc->toString(1)."\n";
77
78
# post it to to the checkedout collection URL
79
my $result = $this->{repository}{client}->post($checkoutUrl, $entryXmlDoc->toString, ATOM_XML_ENTRY_TYPE);
80
81
$this->{xmlDoc} = $result;
82
$this->_initData;
83
$this->reload;
84
85
return $this;
86
}
87
88
=item isCheckedOut() -> $boolean
89
90
Returns true if the document is checked out.
91
92
=cut
93
94
sub isCheckedOut {
95
my $this = shift;
96
97
my $prop = $this->getProperties->{'cmis:isVersionSeriesCheckedOut'};
98
return 0 unless defined $prop;
99
return $prop->getValue;
100
}
101
102
103
=item getCheckedOutBy() -> $userId
104
105
returns the ID who currently has the document checked out.
106
107
=cut
108
109
sub getCheckedOutBy {
110
my $this = shift;
111
112
my $prop = $this->getProperties->{'cmis:versionSeriesCheckedOutBy'};
113
return unless defined $prop;
114
return $prop->getValue;
115
}
116
117
=item getPrivateWorkingCopy() -> L<$cmisDocument|WebService::Cmis::Document>
118
119
retrieves the object using the object ID in the property:
120
cmis:versionSeriesCheckedOutId then uses getObject to instantiate
121
the object.
122
123
=cut
124
125
sub getPrivateWorkingCopy {
126
my $this = shift;
127
128
my $pwcDocId = $this->getProperty('cmis:versionSeriesCheckedOutId');
129
return unless $pwcDocId;
130
return $this->{repository}->getObject($pwcDocId);
131
}
132
133
=item cancelCheckOut() -> L<$this|WebService::Cmis::Document>
134
135
cancels the checkout of this object by retrieving the Private Working
136
Copy (PWC) and then deleting it. After the PWC is deleted, this object
137
will be reloaded to update properties related to a checkout.
138
139
See CMIS specification document 2.2.7.2 cancelCheckOut
140
141
=cut
142
143
sub cancelCheckOut {
144
my $this = shift;
145
146
my $pwcDoc = $this->getPrivateWorkingCopy;
147
$pwcDoc->delete if defined $pwcDoc;
148
149
return $this->getLatestVersion;
150
}
151
152
=item checkIn($checkinComment, %params) -> $this
153
154
checks in this Document which must be a private
155
working copy (PWC).
156
157
See CMIS specification document 2.2.7.3 checkIn
158
159
The following optional arguments are supported:
160
161
=over 4
162
163
=item * major
164
165
=back
166
167
These aren't supported:
168
169
=over 4
170
171
=item * properties
172
173
=item * contentStream
174
175
=item * policies
176
177
=item * addACEs
178
179
=item * removeACEs
180
181
=back
182
183
TODO: support repositories without PWCUpdate capabilities
184
185
=cut
186
187
sub checkIn {
188
my $this = shift;
189
my $checkinComment = shift;
190
191
# build an empty ATOM entry
192
my $xmlDoc = new XML::LibXML::Document('1.0', 'UTF-8');
193
my $entryElement = $xmlDoc->createElementNS(ATOM_NS, "entry");
194
$xmlDoc->setDocumentElement($entryElement);
195
196
# Get the self link
197
# Do a PUT of the empty ATOM to the self link
198
my $url = $this->getSelfLink;
199
200
my $result = $this->{repository}{client}->put($url, $xmlDoc->toString, ATOM_XML_TYPE,
201
"checkin"=>'true',
202
"checkinComment"=>$checkinComment,
203
@_ # here goes our params
204
);
205
206
# reload the current object with the result
207
$this->{xmlDoc} = $result;
208
$this->_initData;
209
$this->reload;
210
211
return $this;
212
}
213
214
=item getContentLink(%params) -> $url
215
216
returns the source link to this document
217
218
The params are added to the url.
219
220
=cut
221
222
sub getContentLink {
223
my $this = shift;
224
my %params = @_;
225
226
my $url = $this->_getDocumentElement->find($CMIS_XPATH_CONTENT_LINK);
227
$url = $this->getLink('enclosure') unless defined $url;
228
return unless defined $url;
229
$url = "".$url;
230
231
my $gotUrlParams = ($url =~ /\?/)?1:0;
232
233
foreach my $key (keys %params) {
234
if ($gotUrlParams) {
235
$url .= '&';
236
} {
237
$url .= '?';
238
$gotUrlParams = 1;
239
}
240
$url .= $key.'='._urlEncode($params{$key});
241
}
242
243
return $url;
244
}
245
246
=item getContentStream($streamId) -> $data
247
248
returns the CMIS service response from invoking the 'enclosure' link.
249
it will return the binary content of the document stored on the server.
250
251
The optional argument:
252
253
=over 4
254
255
=item * streamId: id of the content rendition (TODO: not implemented yet)
256
257
=back
258
259
See CMIS specification document 2.2.4.10 getContentStream
260
261
my $doc = $repo->getObjectByPath("/User homes/jeff/sample.pdf");
262
my $content = $doc->getContentStream;
263
264
my $FILE;
265
unless (open($FILE, '>', $name)) {
266
die "Can't create file $name - $!\n";
267
}
268
print $FILE $text;
269
close($FILE);
270
271
272
=cut
273
274
sub getContentStream {
275
my $this = shift;
276
277
my $url = $this->getContentLink;
278
279
if ($url) {
280
# if the url exists, follow that
281
#print STDERR "url=$url\n";
282
283
my $client = $this->{repository}{client};
284
$client->GET($url, @_);
285
286
my $code = $client->responseCode;
287
return $client->responseContent if $code >= 200 && $code < 300;
288
$client->processErrors;
289
} else {
290
# otherwise, try to return the value of the content element
291
return $this->_getDocumentElement->findvalue("./*[local-name() = 'content' and namespace-uri() = '".ATOM_NS."']");
292
}
293
294
# never reach
295
return;
296
}
297
298
=item getAllVersions(%params) -> $atomFeed
299
300
returns a AtomFeed` of document objects for the entire
301
version history of this object, including any PWC's.
302
303
See CMIS specification document 2.2.7.5 getAllVersions
304
305
The optional filter and includeAllowableActions are
306
supported.
307
308
TODO: is it worth caching these inside?
309
310
=cut
311
312
sub getAllVersions {
313
my $this = shift;
314
315
# get the version history link
316
my $versionsUrl = $this->getLink(VERSION_HISTORY_REL);
317
318
# invoke the URL
319
my $result = $this->{repository}{client}->get($versionsUrl, @_);
320
321
# return the result set
322
require WebService::Cmis::AtomFeed::Objects;
323
return new WebService::Cmis::AtomFeed::Objects(repository=>$this->{repository}, xmlDoc=>$result);
324
}
325
326
=item getRenditions(%params) -> %renditions
327
328
returns a hash of associated Renditions for the specified object. Only
329
rendition attributes are returned, not rendition stream.
330
331
The following optional arguments are currently supported:
332
333
=over 4
334
335
=item * renditionFilter
336
337
=item * maxItems
338
339
=item * skipCount
340
341
=back
342
343
A rendition has the following attributes:
344
345
=over 4
346
347
=item * streamId: Identifies the rendition stream
348
349
=item * mimetype: The MIME type of the rendition stream
350
351
=item * kind: A categorization String associated with the rendition
352
353
=item * length: The length of the rendition stream in bytes (optional)
354
355
=item * title: Human readable information about the rendition (optional)
356
357
=item * height: Typically used for 'image' renditions (expressed as pixels).
358
SHOULD be present if kind = C (optional)
359
360
=item * width: Typically used for 'image' renditions (expressed as pixels).
361
SHOULD be present if kind = C (optional)
362
363
=item * renditionDocumentId: If specified, then the rendition can also be accessed as
364
a document object in the CMIS services. If not set, then the rendition can only
365
be accessed via the rendition services. Referential integrity of this ID is
366
repository-specific. (optional)
367
368
=back
369
370
See CMIS specification document 2.1.4.2 Renditions
371
372
TODO: use
373
374
=cut
375
376
sub getRenditions {
377
my $this = shift;
378
my %params = @_;
379
380
# if Renditions capability is None, return notsupported
381
unless ($this->{repository}->getCapabilities->{'Renditions'}) {
382
throw WebService::Cmis::NotSupportedException("This repository does not support Renditions");
383
}
384
385
unless ($this->{renditions}) {
386
unless ($this->_getDocumentElement->exists($CMIS_XPATH_RENDITIONS)) {
387
# reload including renditions
388
$this->reload(renditionFilter=>'*');
389
}
390
my $elem = $this->_getDocumentElement;
391
$this->{renditions} = ();
392
foreach my $node ($elem->findnodes($CMIS_XPATH_RENDITIONS)) {
393
my $rendition = ();
394
foreach my $child ($node->childNodes) {
395
next unless $child->nodeType == XML_ELEMENT_NODE;
396
my $key = $child->localname;
397
my $val = $child->string_value;
398
#print STDERR "key=$key, value=".($val||'undef')."\n";
399
$rendition->{$key} = $val;
400
}
401
$this->{renditions}{$rendition->{streamId}} = $rendition;
402
}
403
}
404
405
return $this->{renditions};
406
}
407
408
=item getRenditionLink(%params)
409
410
returns a link to the documents rendition
411
412
Use the renditions properties to get a specific one (see L):
413
414
=over 4
415
416
=item * streamId
417
418
=item * mimetype
419
420
=item * kind
421
422
=item * height
423
424
=item * width
425
426
=item * length
427
428
=item * title
429
430
=item * renditionDocumentId
431
432
=back
433
434
my $doc = $repo->getObjectByPath("/User homes/jeff/sample.pdf");
435
my $thumbnailUrl => $doc->getRenditionLink(kind=>"thumbnail");
436
my $iconUrl = $doc->getRenditionLink(kind=>"icon", width=>16);
437
438
=cut
439
440
sub getRenditionLink {
441
my $this = shift;
442
my %params = @_;
443
444
# if Renditions capability is None, return notsupported
445
unless ($this->{repository}->getCapabilities->{'Renditions'}) {
446
throw WebService::Cmis::NotSupportedException("This repository does not support Renditions");
447
}
448
449
my $renditions = $this->getRenditions;
450
foreach my $rendi (values %$renditions) {
451
my $found = 1;
452
foreach my $key (keys %params) {
453
if (defined $rendi->{$key} && $rendi->{$key} !~ /$params{$key}/i) {
454
$found = 0;
455
last;
456
}
457
}
458
next unless $found;
459
460
return $this->getContentLink(streamId=>$rendi->{streamId});
461
}
462
463
return;
464
}
465
466
=item getLatestVersion(%params) -> $document
467
468
returns a cmis Document representing the latest version in the version series.
469
470
See CMIS specification document 2.2.7.4 getObjectOfLatestVersion
471
472
The following optional arguments are supported:
473
474
=over 4
475
476
=item * major
477
478
=item * filter
479
480
=item * includeRelationships
481
482
=item * includePolicyIds
483
484
=item * renditionFilter
485
486
=item * includeACL
487
488
=item * includeAllowableActions
489
490
=back
491
492
$latestDoc = $doc->getLatestVersion;
493
$latestDoc = $doc->getLatestVersion(major=>1);
494
495
print $latestDoc->getProperty("cmis:versionLabel")."\n";
496
497
=cut
498
499
sub getLatestVersion {
500
my $this = shift;
501
my %params = @_;
502
503
my $major = delete $params{major};
504
$params{returnVersion} = $major?'latestmajor':'latest';
505
506
$this->_initData;
507
508
$params{id} = $this->getProperty("cmis:versionSeriesId");
509
return $this->reload(%params);
510
}
511
512
=item copy($targetFolder, $propertyList, $versionState) -> $cmisDocument
513
514
TODO: This is not yet implemented.
515
516
Creates a document object as a copy of the given source document in the (optionally)
517
specified location.
518
519
The $targetFolder specifies the folder that becomes the parent
520
of the new document. This parameter must be specified if the repository does not
521
have the "unfiling" capability.
522
523
The $propertyList is a list of WebService::Cmis::Property objects optionally specifieds
524
the propeties about to change in the newly created Document object.
525
526
Valid values for $versionState are:
527
528
=over 4
529
530
=item * none: the document is created as a non-versionable object
531
532
=item * checkedout: the document is created in checked-out state
533
534
=item * major (default): the document is created as a new major version
535
536
=item * minor: the document is created as a minor version
537
538
=back
539
540
The following optional arguments are not yet supported:
541
542
=over 4
543
544
=item * policies
545
546
=item * addACEs
547
548
=item * removeACEs
549
550
=back
551
552
See CMIS specification document 2.2.4.2 (createDocumentFromSource)
553
554
=cut
555
556
sub copy { throw WebService::Cmis::NotImplementedException; }
557
558
=item getPropertiesOfLatestVersion
559
560
TODO: This is not yet implemented.
561
562
=cut
563
564
sub getPropertiesOfLatestVersion { throw WebService::Cmis::NotImplementedException; }
565
566
=item setContentStream(%params) -> $this
567
568
This sets the content stream of a document.
569
570
The following parameters are supported:
571
572
=over 4
573
574
=item * contentFile: the absolute path to the file to be used.
575
576
=item * contentData: the data to be posted to the documents content stream link.
577
use either C or C.
578
579
=item * contentType: the mime type of the data. will be guessed automatically if not specified manually.
580
581
=item * overwriteFlag: if 'true' (default), replace the existing content stream.
582
if 'false', set the input contentStream if the object currently does not have a content-stream.
583
584
=item * changeToken
585
586
=back
587
588
See CMIS specification document 2.2.4.18 setContentStream
589
590
=cut
591
592
sub setContentStream {
593
my $this = shift;
594
my %params = @_;
595
596
my $contentStreamUpdatability = $this->{repository}->getCapabilities->{'ContentStreamUpdatability'};
597
#print STDERR "contentStreamUpdatability=$contentStreamUpdatability\n";
598
599
throw WebService::Cmis::NotSupportedException("This repository does not allow to set the content stream")
600
if $contentStreamUpdatability eq 'none';
601
602
my $contentFile = delete $params{contentFile};
603
my $contentData = delete $params{contentData};
604
my $contentType = delete $params{contentType};
605
606
unless (defined $contentData) {
607
my $fh;
608
609
open($fh, '<', $contentFile)
610
or throw Error::Simple("can't open file $contentFile"); # SMELL: use a custom exception
611
612
local $/ = undef;# set to read to EOF
613
$contentData = <$fh>;
614
close($fh);
615
$contentData = '' unless $contentData; # no undefined
616
}
617
618
unless (defined $contentType) {
619
620
# get the file mage used for checking
621
require File::MMagic;
622
my $fileMage = new File::MMagic;
623
624
$contentType = $fileMage->checktype_contents($contentData);
625
626
# contentType fallback
627
$contentType = 'application/binary' unless defined $contentType;
628
}
629
630
631
# SMELL: not sure whether we need to encode or not
632
#require MIME::Base64;
633
#$contentData = MIME::Base64::encode_base64($contentData);
634
635
my $url = $this->getContentLink;
636
throw Error::Simple("Can't find content link for ".$this->getId) unless $url;
637
638
# SMELL: CMIS specification document 2.2.4.18 and 3.1.9 don't agree whether to return the new object Id or not.
639
# In addition it is not clear whether setContentStream should create a new revision or not.
640
# So we make sure the document is checked out and will create a new minor version at least.
641
642
if ($contentStreamUpdatability eq 'pwconly') {
643
$this->checkOut unless $this->isCheckedOut;
644
}
645
646
my $result = $this->{repository}{client}->put($url, $contentData, $contentType, %params) || '';
647
648
# reload with this result
649
if (defined $result && $result ne '') {
650
$this->{xmlDoc} = $result;
651
$this->_initData;
652
}
653
654
$this->reload;
655
656
# make sure this is checked in
657
if ($contentStreamUpdatability eq 'pwconly') {
658
$this->checkIn("setting content stream", major=>0) if $this->isCheckedOut;
659
}
660
661
return $this;
662
}
663
664
=item deleteContentStream
665
666
TODO: This is not yet implemented.
667
668
See CMIS specification document 2.2.4.17 deleteContentStream
669
670
=cut
671
672
sub deleteContentStream { throw WebService::Cmis::NotImplementedException; }
673
674
=back
675
676
=head1 COPYRIGHT AND LICENSE
677
678
Copyright 2012-2013 Michael Daum
679
680
This module is free software; you can redistribute it and/or modify it under
681
the same terms as Perl itself. See F.
682
683
=cut
684
685
1;