File Coverage

blib/lib/Repository/Simple.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 Repository::Simple;
2              
3 9     9   39290 use strict;
  9         20  
  9         366  
4 9     9   49 use warnings;
  9         16  
  9         445  
5              
6             our $VERSION = '0.06';
7              
8 9     9   47 use Carp;
  9         17  
  9         956  
9 9     9   7920 use Readonly;
  0            
  0            
10             use Repository::Simple::Engine qw( :exists_constants );
11             use Repository::Simple::Node;
12             use Repository::Simple::Permission;
13             use Repository::Simple::Util qw( basename dirname normalize_path );
14              
15             our @CARP_NOT = qw( Repository::Simple::Util );
16              
17             =head1 NAME
18              
19             Repository::Simple - Simple heirarchical repository for Perl
20              
21             =head1 SYNOPSIS
22              
23             use Repository::Simple;
24              
25             my $repository = Repository::Simple->attach(
26             FileSystem => root => /home/foo
27             );
28              
29             =head1 DESCRIPTION
30              
31             B This software is still in development and the interface WILL CHANGE.
32              
33             This is the main module of a hierarchical repository system, which is loosely based upon the L module I've written combined with ideas from the JSR 170, a.k.a. Content Repository API for the Java API Specification.
34              
35             The goal of this package is to provide a content repository system with a similar feature set. I think it would be a good goal to aim for loose compatibility, but it is not my intent to adhere to the strict letter of that standard. See L for details of the major deviations.
36              
37             =head1 TERMINOLOGY
38              
39             This is a glossary of the terms used by this library. Many of these have been borrowed from the JSR 170 terminology.
40              
41             =over
42              
43             =item name
44              
45             A name is given to every node and path in the tree. A name may contain any number of letters, numbers, underscores, dashes, and colons. No other letter is permitted in a name.
46              
47             =item node
48              
49             Data in the repository is associated with objects called nodes. Each node has a parent which roots it in the hierarchy. Each node has a name. A node may have zero or more properties associated with it. A node may have zero or more child nodes associated with it.
50              
51             =item node type
52              
53             The node type determines when/how a node may be changed and the acceptable names and types of child properties and nodes.
54              
55             =item parent
56              
57             Relative to a given node or property, the parent is the node that is one-level higher than the given node or property.
58              
59             =item path
60              
61             A path is a collection of names separated by slashes ("/") used to refer to given node or property. The name of a node or property will be the right-most element of the path (after the last slash). The parent of a node or property is refered to by the path with the last slash and last name stripped off.
62              
63             =item property
64              
65             A property is a field associated with a node object. Each field has a single parent node. Each field has a name. Each field has a value.
66              
67             =item property type
68              
69             A property type determines when/how a property may be changed and what values are acceptable, via a selected value type.
70              
71             =item repository
72              
73             The repository is the name for this storage API, specifically for the repository, node, property, and value classes.
74              
75             =item storage engine
76              
77             The storage engine is the back-end storage device a repository refers to. The storage engine is responsible for actually reading from and writing to the storage device. The repository can be used without direct knowledge of the storage device in use.
78              
79             =item type
80              
81             Type is the generic term referred to describe the permitted nature of an object in the system. There are three kinds of type: node type, property type, and value type.
82              
83             =item value
84              
85             A value is the data associated with a property.
86              
87             =item value type
88              
89             A value type restricts the kinds of values that can be associated with a property. It may define how a value is checked for correctness and may define methods for serializing and deserializing values of the given type.
90              
91             =back
92              
93             =head1 CONTENT REPOSITORY
94              
95             This package provides the entry point to an API for implementing content repository engines, and for storing nodes, properties, and values into those engines. As of this writing a single engine is provided, which accesses a native file system repository. Other repositories are planned.
96              
97             The basic idea is that every content repository is comprised of objects. We call these objects "nodes". Nodes are arranged in a rooted hierarchy. At the top is a single node named "/". Each node may have zero or more child nodes under them.
98              
99             In addition to nodes, there are fields associated with nodes, called "properties". Each property has a name and value.
100              
101             A given content repository may only store items that follow a specific schema. Each node has an associated node type. Each property has an associated property type. Each value stored in a property has an associated value type. The node and property types determine what a valid hierarchy will look like for a repository. The value types determine how a value should be stored and how it should be represented after it is loaded.
102              
103             The functionality available in a given repository is determined by the content repository engine which is used to run it. There is a back-end API for creating new kinds of storage engines. At this time, the following engines are implemented or planned:
104              
105             =over
106              
107             =item L
108              
109             Not yet implemented. This engine reads and stores hierarchies inside of SQL databases.
110              
111             =item L
112              
113             This storage engine maps a hierarchy into the native file system.
114              
115             =item L
116              
117             Not yet implemented. This storage engine allows one or more engines to be layered over top of each other.
118              
119             =item L
120              
121             This storage engine reads and stores hierarchies in transient memory structures.
122              
123             =item L
124              
125             Not yet implemented. This storage engine simply wraps another storage engine as a mechanism to simplify meta-engine extensions.
126              
127             =item L
128              
129             Not yet implemented. This storage engine allows for VFS-like mounting of one or more engines via a mount table.
130              
131             =item L
132              
133             Not yet implemented. This storage engine reads and stores data in an XML file.
134              
135             =back
136              
137             As of this writing, only read operations have been implemented. Write operations are planned, but haven't been designed or implemented yet.
138              
139             =head2 REPOSITORY ENGINE
140              
141             Repository engines are implemented via a bridge pattern. Rather than having each engine implement several packages covering nodes, properties, values, and other parts, each engine is a single package containing definitions for all the methods required to access the repository's storage.
142              
143             Normally, you will not interact with the repository engine directly after you instantiate it using the repository connection factory method, C:
144              
145             my $repository = Repository::Simple->attach(...);
146              
147             The returned repository object, C<$repository> in this example, is an instance of this class, L, which holds an internal reference to the engine. Thus, you do not usually need to be aware of how the engine works after instantiation.
148              
149             If you are interested in building a repository engine, the details of repository engine design may be found in L.
150              
151             =head2 THIS CLASS
152              
153             This class provides the entry point into the repository API. The typical way of getting a reference to an instance of this class is to use the L method to connect to a repository. This method of returns an instance of L, which encapsulates the requested repository engine connection.
154              
155             As an alternative, you may also instantiate an engine directly:
156              
157             my $engine = MyProject::Content::Engine->new;
158             my $repository = Repository::Simple->new($engine);
159              
160             This shouldn't be necessary in most cases though, since this is the same as:
161              
162             my $repository
163             = Repository::Simple->attach('MyProject::Content::Engine');
164              
165             =head1 METHODS
166              
167             =over
168              
169             =item $repository = Repository::Simple-Eattach($engine, ...)
170              
171             This will attach to a repository via the named engine, C<$engine>. The repository object representing that storage is returned.
172              
173             If the C<$engine> does not contain any colons, then the package "C" is loaded. Otherwise, the C<$engine> is loaded and its C method is used.
174              
175             Any additional arguments passed to this method are then passed to the C method of the engine.
176              
177             See L if you are interested in the guts.
178              
179             =cut
180              
181             sub attach {
182             my ($class, $engine) = @_;
183              
184             $engine =~ /[\w:]+/
185             or croak "The given content repository engine, $engine, "
186             .'does not appear to be a package name.';
187              
188             # XXX should this be configurable?
189             $engine =~ /:/
190             or $engine = "Repository::Simple::Engine::$engine";
191              
192             eval "use $engine";
193             warn "Failed to load package for engine, $engine: $@" if $@;
194              
195             my $instance = eval { $engine->new(@_) };
196             if ($@) {
197             $@ =~ s/ at .*//s;
198             croak $@ if $@;
199             }
200              
201             return Repository::Simple->new($instance);
202             }
203              
204             =item $repository = Repository::Simple-Enew($engine)
205              
206             Given an engine, this constructor wraps the engine with a repository object.
207              
208             =cut
209              
210             sub new {
211             my ($class, $engine) = @_;
212             return bless { engine => $engine }, $class;
213             }
214              
215             =item $engine = $repository-Eengine
216              
217             Returns a reference to the engine this repository is using.
218              
219             =cut
220              
221             sub engine {
222             my ($self) = @_;
223             return $self->{engine};
224             }
225              
226             =item $node_type = $repository-Enode_type($name)
227              
228             Returns the L object for the given C<$name> or returns C if no such type exists in the repository.
229              
230             =cut
231              
232             sub node_type {
233             my ($self, $type_name) = @_;
234              
235             if (!defined $type_name) {
236             croak 'no type name given for lookup';
237             }
238              
239             return $self->engine->node_type_named($type_name);
240             }
241              
242             =item $property_type = $repository-Eproperty_type($name)
243              
244             Returns the L object for the given C<$name> or returns C if no such type exists in the repository.
245              
246             =cut
247              
248             sub property_type {
249             my ($self, $type_name) = @_;
250              
251             if (!defined $type_name) {
252             croak 'no type name given for lookup';
253             }
254              
255             return $self->engine->property_type_named($type_name);
256             }
257              
258             =item $root_node = $repository-Eroot_node
259              
260             Return the root node in the repository.
261              
262             =cut
263              
264             sub root_node {
265             my $self = shift;
266              
267             $self->check_permission("/", $READ);
268              
269             return Repository::Simple::Node->new($self, "/");
270             }
271              
272             =item $item = $repository-Eget_item($path)
273              
274             Return the node (L) or property (L) found at the given path. This method returns C if the given path points to nothing.
275              
276             =cut
277              
278             sub get_item {
279             my ($self, $path) = @_;
280              
281             $path = normalize_path('/', $path);
282              
283             $self->check_permission($path, $READ);
284              
285             my $exists = $self->engine->path_exists($path);
286              
287             if ($exists == $NODE_EXISTS) {
288             return Repository::Simple::Node->new($self, $path);
289             }
290              
291             elsif ($exists == $PROPERTY_EXISTS) {
292             my $property_name = basename($path);
293             my $parent_path = dirname($path);
294             my $parent = Repository::Simple::Node->new($self, $parent_path);
295             return Repository::Simple::Property->new($parent, $property_name);
296             }
297              
298             else {
299             return undef;
300             }
301             }
302              
303             =item %namespaces = $repository-Enamespaces
304              
305             Determine the meaning of the name prefixes used by the engine. This returns a hash of all namespace information currently used by the storage engine.
306              
307             =cut
308              
309             sub namespaces {
310             my ($self) = @_;
311             return %{ $self->engine->namespaces };
312             }
313              
314             =item $repository->check_permission($path, $action)
315              
316             This method will C if the specified action, C<$action>, is not permitted on the given path, C<$path>, due to access restrictions by the current attached session. The C<$path> is an absolute path in the repository.
317              
318             The C<$action> must be one of the following constants:
319              
320             =over
321              
322             =item $ADD_NODE
323              
324             Use this to see if the current session is permitted to create a node at C<$path>.
325              
326             =item $SET_PROPERTY
327              
328             Use this to see if the current session is permitted to modify the property at C<$path>.
329              
330             =item $REMOVE
331              
332             Use this to see if the current session is permitted to remove the node or property at C<$path>.
333              
334             =item $READ
335              
336             Use this to see if the current session is permitted to read the data at the node or property at C<$path>.
337              
338             =back
339              
340             The constants may be imported from this package individually or as a group using the ":permission_constants" tag:
341              
342             use Repository::Simple qw( $READ $ADD_NODE );
343              
344             # OR
345            
346             use Repository::Simple qw( :permission_constants );
347              
348             =cut
349              
350             sub check_permission {
351             my ($self, $path, $action) = @_;
352             my $engine = $self->engine;
353              
354             # Sanity checks
355             croak "No path given" unless defined $path;
356             croak "No action given" unless defined $action;
357             croak qq(Invalid action "$action" given)
358             unless $action =~ /^(?:$READ|$SET_PROPERTY|$REMOVE|$ADD_NODE)$/;
359              
360             # Normalize path
361             $path = normalize_path('/', $path);
362              
363             # What is it?
364             my $exists = $engine->path_exists($path);
365              
366             # Make sure this is a valid node action
367             if ($exists == $NODE_EXISTS) {
368             my $node_type = $engine->node_type_of($path);
369              
370             if ($action eq $ADD_NODE) {
371             croak qq(Error: cannot create node "$path": it already exists);
372             }
373              
374             elsif ($action eq $SET_PROPERTY) {
375             croak qq(Error: cannot set property "$path": a node exists at ),
376             q(that path);
377             }
378              
379             elsif ($action eq $REMOVE) {
380             if (!$node_type->removable) {
381             croak qq(Error: cannot remove node "$path": it is not ),
382             q(removable);
383             }
384             }
385             }
386              
387             # Make sure this is a valid property action
388             elsif ($exists == $PROPERTY_EXISTS) {
389             my $property_type = $engine->property_type_of($path);
390              
391             if ($action eq $ADD_NODE) {
392             croak qq(Error: cannot create node "$path": a property exists at ),
393             q(that path);
394             }
395              
396             elsif ($action eq $SET_PROPERTY) {
397             if (!$property_type->updatable) {
398             croak qq(Error: cannot update property "$path": it is not ),
399             q(updatable);
400             }
401             }
402              
403             elsif ($action eq $REMOVE) {
404             if (!$property_type->removable) {
405             croak qq(Error: cannot remove property "$path": it is not ),
406             q(removable);
407             }
408             }
409             }
410              
411             # Doesn't exists, make sure it's a sane thing to say
412             else {
413             # Check for parent
414             my $parent_path = dirname($path);
415             my $parent_exists = $engine->path_exists($parent_path);
416              
417             if ($parent_exists == $PROPERTY_EXISTS) {
418             if ($action eq $ADD_NODE) {
419             croak qq(Error: cannot create node "$path": the parent path ),
420             qq("$parent_path" is a property);
421             }
422              
423             elsif ($action eq $SET_PROPERTY) {
424             croak qq(Error: cannot set property "$path": the parent path ),
425             qq("$parent_path" is a property);
426             }
427             }
428              
429             elsif ($parent_exists == $NOT_EXISTS) {
430             if ($action eq $ADD_NODE) {
431             croak qq(Error: cannot create node "$path": the parent path ),
432             qq("$parent_path" does not exist);
433             }
434             }
435              
436             if ($action eq $REMOVE) {
437             croak qq(Error: cannot remove item "$path": the path does not ),
438             q(exist);
439             }
440              
441             elsif ($action eq $READ) {
442             croak qq(Error: cannot read item "$path": the path does not exist);
443             }
444             }
445              
446             # Finally, actually check the permissions
447             if (!$self->engine->has_permission($path, $action)) {
448             croak qq(Access denied: current session does not have "$action" ),
449             qq(permission on "$path".);
450             }
451             }
452              
453             =back
454              
455             =head1 DIFFERENCES FROM JSR 170
456              
457             Here are some specific differences between this implementation and the JSR 170 specification.
458              
459             B This implementation doesn't attempt to define any specifics when it comes to node types, property types, or value types. The way these are used is up to the storage engines.
460              
461             In particular, it is possible to create value types that are in nearly any data format, rather than being restricted to strings, binary streams, longs, doubles, booleans, dates, names, paths, and references. For example, you could store arbitrarily complex Perl types if you defined a type extension to use YAML to store data into files.
462              
463             B This library doesn't implement most of the classes required by a JCR implementation.
464              
465             =head1 TO DO
466              
467             There are a number of tasks remaining to do on this project. Here are a few of the big tasks:
468              
469             =over
470              
471             =item *
472              
473             Add better support for naming and namespaces.
474              
475             =item *
476              
477             Design and implement the storage API so repositories can write as well as read data. Then, update all existing implementations to handle it.
478              
479             =item *
480              
481             Implement several more data types including rs:string, rs:binary, rs:long, rs:double, rs:datetime, rs:boolean, rs:name, rs:path, and rs:reference.
482              
483             =item *
484              
485             Add support for creating node references and performing lookups on nodes by reference for repositories that support such operations.
486              
487             =item *
488              
489             Add support for indexing and search.
490              
491             =item *
492              
493             Add support for globbing, XPath, and SQL selection as indexers and search methods.
494              
495             =item *
496              
497             Observation.
498              
499             =item *
500              
501             Version control.
502              
503             =item *
504              
505             Implement the DBI repository engine.
506              
507             =item *
508              
509             Implement the layered repository engine.
510              
511             =item *
512              
513             Implement the memory repository engine.
514              
515             =item *
516              
517             Implement the passthrough repository engine.
518              
519             =item *
520              
521             Implement the table repository engine.
522              
523             =item *
524              
525             Implement the XML repository engine.
526              
527             =back
528              
529             =head1 AUTHOR
530              
531             Andrew Sterling Hanenkamp, Ehanenkamp@cpan.orgE
532              
533             =head1 LICENSE AND COPYRIGHT
534              
535             Copyright 2005 Andrew Sterling Hanenkamp Ehanenkamp@cpan.orgE. All
536             Rights Reserved.
537              
538             This module is free software; you can redistribute it and/or modify it under
539             the same terms as Perl itself. See L.
540              
541             This program is distributed in the hope that it will be useful, but WITHOUT
542             ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
543             FOR A PARTICULAR PURPOSE.
544              
545             =cut
546              
547             1