Permalink
Cannot retrieve contributors at this time
801 lines (642 sloc)
18.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| =head1 NAME | |
| EPrints::Apache::REST | |
| =cut | |
| ###################################################################### | |
| # | |
| # EPrints::Apache::REST | |
| # | |
| ###################################################################### | |
| # | |
| # | |
| ###################################################################### | |
| package EPrints::Apache::REST; | |
| use EPrints::Apache::AnApache; # exports apache constants | |
| use strict; | |
| use warnings; | |
| sub handler | |
| { | |
| my( $r ) = @_; | |
| my $repository = $EPrints::HANDLE->current_repository; | |
| my $uri = $r->uri; | |
| my $urlpath = $repository->config( "http_root" ); | |
| $uri =~ s! ^$urlpath !!x; | |
| $uri =~ s/^\/rest//; | |
| if( $uri eq "" ) | |
| { | |
| return redir_add_slash( $repository ); | |
| } | |
| if( ! $uri =~ m/^\// ) | |
| { | |
| return NOT_FOUND; | |
| } | |
| my @path = split( '/', $uri ); | |
| shift @path; | |
| if( $uri =~ m/\/$/ ) { push @path, ""; } | |
| return serve_top_level( $repository, @path ); | |
| } | |
| sub redir_add_slash | |
| { | |
| my( $repository ) = @_; | |
| return $repository->redirect( $repository->get_uri."/" ); | |
| } | |
| ## SERVE FUNCTIONS | |
| sub serve_top_level | |
| { | |
| my( $repository, @path ) = @_; | |
| # / | |
| if( @path == 1 && $path[0] eq "" ) | |
| { | |
| #my @ds_ids = $repository->get_dataset_ids; | |
| my @ds_ids = qw/ eprint user subject /; | |
| my $h = "<ul>\n"; | |
| foreach my $ds_id ( @ds_ids ) | |
| { | |
| my $dataset = $repository->dataset( $ds_id ); | |
| next if $dataset->confid ne $ds_id; | |
| $h .= "<li><a href='$ds_id/'>".EPrints::XML::to_string( $dataset->render_name( $repository ) )."</a></li>\n"; | |
| } | |
| $h .= "</ul>\n"; | |
| return send_html( $repository, $h, "Datasets" ); | |
| } | |
| # /eprint | |
| if( @path == 1 ) | |
| { | |
| return redir_add_slash( $repository ); | |
| } | |
| # /eprint/ | |
| my $dataset_id = shift @path; | |
| my $dataset = $repository->dataset( $dataset_id ); | |
| if( !defined $dataset ) | |
| { | |
| return 404; | |
| } | |
| return serve_dataset( $repository, $dataset, @path ); | |
| } | |
| sub serve_dataset | |
| { | |
| my( $repository, $dataset, @path ) = @_; | |
| my $file = shift @path; | |
| if( scalar @path == 0 ) | |
| { | |
| # / | |
| if( $file eq "" ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| my $sortfn = sub { $a <=> $b }; | |
| if( $dataset->id eq "subject" ) | |
| { | |
| $sortfn = sub { $a cmp $b }; | |
| } | |
| my $ul = "<ul>\n"; | |
| foreach my $id ( sort $sortfn @{$dataset->get_item_ids( $repository )} ) | |
| { | |
| $ul .= "<li><a href='$id/'>$id/</a></li>\n"; | |
| $ul .= "<li><a href='$id.xml'>$id.xml</a></li>\n"; | |
| } | |
| $ul .= "</ul>\n"; | |
| my $title = EPrints::XML::to_string( $dataset->render_name( $repository ) )." DataSet"; | |
| return send_html( $repository, $ul, $title ); | |
| } | |
| # /23 | |
| if( $file =~ m/^\d+$/ ) | |
| { | |
| return redir_add_slash( $repository ); | |
| } | |
| my( $item_id, $format) = split( /\./, $file, 2 ); | |
| my $object = $dataset->get_object( $repository, $item_id ); | |
| if( !defined $object ) | |
| { | |
| return 404; | |
| } | |
| if( defined $format && $format eq "xml" ) | |
| { | |
| return unless allowed_methods( $repository, "GET","PUT" ); | |
| my $method = $ENV{REQUEST_METHOD}; | |
| if( $method eq "GET" ) | |
| { | |
| return get_dataobj_xml( $repository, $object, $object ); | |
| } | |
| if( $method eq "PUT" ) | |
| { | |
| return put_dataobj_xml( $repository, $object, $object ); | |
| } | |
| return 500; # should not happen! | |
| } | |
| return 404; | |
| } | |
| # /23/ | |
| my $object = $dataset->get_object( $repository, $file ); | |
| # /abc/ | |
| if( !defined $object && $dataset->id eq "user" ) | |
| { | |
| $object = $repository->user_by_username( $file ); | |
| } | |
| if( !defined $object ) | |
| { | |
| return 404; | |
| } | |
| return serve_dataobj( $repository, $object, $object, @path ); | |
| } | |
| sub serve_dataobj | |
| { | |
| my( $repository, $object, $rights_object, @path ) = @_; | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/get", $repository, $rights_object ); | |
| my $file = shift @path; | |
| if( scalar @path == 0 ) | |
| { | |
| # / | |
| if( $file eq "" ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| my $c = "<ul>\n"; | |
| foreach my $field ( $object->dataset->get_fields ) | |
| { | |
| next if( $field->get_property( "sub_name" ) ); | |
| next if( $field->isa( "EPrints::MetaField::Secret" ) ); | |
| my $name = $field->get_name; | |
| $c.="<li><a href='$name.xml'>$name.xml</a></li>"; | |
| if( $field->get_property( "multiple" ) | |
| || $field->is_type( "compound","subobject","name" ) ) | |
| { | |
| $c.="<li><a href='$name/'>$name/</a></li>\n"; | |
| } | |
| else | |
| { | |
| $c.="<li><a href='$name.txt'>$name.txt</a></li>\n"; | |
| } | |
| } | |
| $c.= "</ul>\n"; | |
| return send_html( $repository, $c ); | |
| } | |
| my( $field_name, $format) = split( /\./, $file, 2 ); | |
| my $field = $object->dataset->get_field( $field_name ); | |
| return 404 if( !defined $format ); | |
| return 404 if( !defined $field ); | |
| return 404 if( $field->get_property( "sub_name" )); | |
| return 403 if( $field->isa( "EPrints::MetaField::Secret" ) ); | |
| if( $field->get_property( "multiple" ) | |
| || $field->is_type( "compound","subobject","name" ) ) | |
| { | |
| if( $file eq $field_name ) # ie no . in it | |
| { | |
| return redir_add_slash( $repository ); | |
| } | |
| } | |
| else | |
| { | |
| if( $format eq "txt" ) | |
| { | |
| # /title.txt | |
| return unless allowed_methods( $repository, "GET","PUT" ); | |
| my $method = $ENV{REQUEST_METHOD}; | |
| if( $method eq "GET" ) | |
| { | |
| return get_field_txt( $repository, $object, $rights_object, $field ); | |
| } | |
| if( $method eq "PUT" ) | |
| { | |
| return put_field_txt( $repository, $object, $rights_object, $field ); | |
| } | |
| return 500; # never happens | |
| } | |
| } | |
| if( $format eq "xml" ) | |
| { | |
| # /title.xml | |
| return unless allowed_methods( $repository, "GET","PUT" ); | |
| my $method = $ENV{REQUEST_METHOD}; | |
| if( $method eq "GET" ) | |
| { | |
| return get_field_xml( $repository, $object, $rights_object, $field ); | |
| } | |
| if( $method eq "PUT" ) | |
| { | |
| return put_field_xml( $repository, $object, $rights_object, $field ); | |
| } | |
| return 500; | |
| } | |
| return 404; | |
| } | |
| # /title/ | |
| my $field = $object->dataset->get_field( $file ); | |
| return 404 if( !defined $field ); | |
| my $v = $object->get_value( $file ); | |
| return serve_field( $repository, $object, $rights_object, $field, $v, @path ); | |
| } | |
| sub serve_field | |
| { | |
| my( $repository, $object, $rights_object, $field, $value, @path ) = @_; | |
| if( !$field->get_property( "multiple" ) ) | |
| { | |
| return serve_field_single( $repository, $object, $rights_object, $field, $value, @path ); | |
| } | |
| my $file = shift @path; | |
| if( scalar @path == 0 ) | |
| { | |
| # / | |
| if( $file eq "" ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| my $c = "<ul>\n"; | |
| $c.= "<li><a href='size.txt'>size.txt</a></li>\n"; | |
| for( my $i=0; $i<scalar @{$value}; ++$i ) | |
| { | |
| my $n = $i + 1; | |
| if( $field->is_type( "subobject" ) ) | |
| { | |
| $c.="<li><a href='$n/'>$n/</a></li>\n"; | |
| $c.="<li><a href='$n.xml'>$n.xml/</a></li>\n"; | |
| } | |
| elsif( $field->is_type( "compound","name" ) ) | |
| { | |
| $c.="<li><a href='$n/'>$n/</a></li>\n"; | |
| } | |
| else | |
| { | |
| $c.="<li><a href='$n.txt'>$n.txt</a></li>\n"; | |
| } | |
| } | |
| $c.="</ul>\n"; | |
| return send_html( $repository, $c ); | |
| } | |
| if( $file eq "size.txt" ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| return send_plaintext( $repository, scalar( @{$value} ) ); | |
| } | |
| my( $n, $format ) = split( /\./, $file, 2 ); | |
| if( $format eq "xml" && $field->is_type( "subobject" ) ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| return send_xml( $repository, $object->export( "XML" ) ); | |
| } | |
| if( !EPrints::Utils::is_set( $value->[$n-1] ) ) | |
| { | |
| return 404; | |
| } | |
| if( $field->is_type( "compound","subobject","name" ) ) | |
| { | |
| if( $file eq $n ) # ie no . in it | |
| { | |
| return redir_add_slash( $repository ); | |
| } | |
| } | |
| else | |
| { | |
| if( $format eq "txt" ) | |
| { | |
| # /3.txt | |
| return unless allowed_methods( $repository, "GET" ); | |
| return send_plaintext( $repository, $value->[$n-1] ); | |
| } | |
| } | |
| return 404; | |
| } | |
| if( $file !~ m/^\d+$/ ) | |
| { | |
| return 404; | |
| } | |
| my $i = $file-1; | |
| if( !EPrints::Utils::is_set( $value->[$i] ) ) | |
| { | |
| return 404; | |
| } | |
| # /3/ | |
| return serve_field_single( $repository, $object, $rights_object, $field, $value->[$i], @path ); | |
| } | |
| sub serve_field_single | |
| { | |
| my( $repository, $object, $rights_object, $field, $value, @path ) = @_; | |
| if( $field->is_type( "subobject" ) ) | |
| { | |
| return serve_subobject( $repository, $object, $rights_object, $field, $value, @path ); | |
| } | |
| if( $field->is_type( "compound" ) ) | |
| { | |
| return serve_compound( $repository, $object, $rights_object, $field, $value, @path ); | |
| } | |
| if( $field->is_type( "name" ) ) | |
| { | |
| return serve_name( $repository, $object, $rights_object, $field, $value, @path ); | |
| } | |
| $repository->log( "REST: Unknown field type in serve_field_single()" ); | |
| return 500; | |
| } | |
| sub serve_subobject | |
| { | |
| my( $repository, $object, $rights_object, $field, $value, @path ) = @_; | |
| my $ds = $repository->dataset( $field->get_property('datasetid') ); | |
| return serve_dataobj( $repository, $value, $rights_object, @path ); | |
| } | |
| sub serve_compound | |
| { | |
| my( $repository, $object, $rights_object, $field, $value, @path ) = @_; | |
| my $file = shift @path; | |
| my $f = $field->get_property( "fields_cache" ); | |
| my %fieldname_to_alias = $field->get_fieldname_to_alias; | |
| my %alias_to_fieldname = $field->get_alias_to_fieldname; | |
| if( scalar @path == 0 ) | |
| { | |
| if( $file eq "") | |
| { | |
| # / | |
| return unless allowed_methods( $repository, "GET" ); | |
| my $c = "<ul>\n"; | |
| foreach my $sub_field ( @{$f} ) | |
| { | |
| my $fieldname = $sub_field->get_name; | |
| my $alias = $fieldname_to_alias{$fieldname}; | |
| if( $sub_field->is_type( "compound","subobject","name" ) ) | |
| { | |
| $c.="<li><a href='$alias/'>$alias/</a></li>\n"; | |
| } | |
| else | |
| { | |
| $c.="<li><a href='$alias.txt'>$alias.txt</a></li>\n"; | |
| } | |
| } | |
| $c .= "</ul>\n"; | |
| return send_html( $repository, $c ); | |
| } | |
| my( $part, $format ) = split( /\./, $file, 2 ); | |
| my $fieldname = $alias_to_fieldname{$part}; | |
| return 404 if( !defined $fieldname ); | |
| my $sub_field = $object->dataset->get_field( $fieldname ); | |
| if( $part eq $file ) | |
| { | |
| return redir_add_slash( $repository ); | |
| } | |
| # /foo.txt | |
| if( !$sub_field->is_type( "compound","subobject","name" ) && $format eq "txt" ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| return send_plaintext( $repository, $value->{$part} ); | |
| } | |
| return 404; | |
| } | |
| my $fieldname = $alias_to_fieldname{$file}; | |
| if( !defined $fieldname ) | |
| { | |
| return 404; | |
| } | |
| my $sub_field = $object->dataset->get_field( $fieldname ); | |
| # /foo/ | |
| if( $sub_field->is_type( "compound","subobject","name" ) ) | |
| { | |
| return serve_field_single( $repository, $object, $rights_object, $sub_field, $value->{$file}, @path ); | |
| } | |
| return 404; | |
| } | |
| sub serve_name | |
| { | |
| my( $repository, $object, $rights_object, $field, $value, @path ) = @_; | |
| my $file = shift @path; | |
| if( scalar @path == 0 ) | |
| { | |
| # / | |
| if( $file eq "" ) | |
| { | |
| return unless allowed_methods( $repository, "GET" ); | |
| my $c = <<END; | |
| <ul> | |
| <li><a href='honourific.txt'>honourific.txt</a></li> | |
| <li><a href='given.txt'>given.txt</a></li> | |
| <li><a href='family.txt'>family.txt</a></li> | |
| <li><a href='lineage.txt'>lineage.txt</a></li> | |
| </ul> | |
| END | |
| return send_html( $repository, $c ); | |
| } | |
| if( $file =~ m/^(honourific|given|family|lineage)\.txt$/ ) | |
| { | |
| # /given.txt | |
| return unless allowed_methods( $repository, "GET" ); | |
| return send_plaintext( $repository, $value->{$1} || ""); | |
| } | |
| } | |
| return 404; | |
| } | |
| ######## | |
| # END OF serve_ | |
| sub render_html | |
| { | |
| my( $html, $title ) = @_; | |
| if( defined $title ) | |
| { | |
| $title = "EPrints REST: $title"; | |
| } | |
| else | |
| { | |
| $title = "EPrints REST"; | |
| } | |
| return <<END; | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | |
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
| <html xmlns="http://www.w3.org/1999/xhtml"> | |
| <head> | |
| <title>$title</title> | |
| <style type="text/css"> | |
| body { font-family: sans-serif; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>$title</h1> | |
| $html | |
| </body> | |
| </html> | |
| END | |
| } | |
| sub send_html | |
| { | |
| my( $repository, $html, $title ) = @_; | |
| binmode( *STDOUT, ":utf8" ); | |
| $repository->send_http_header( "content_type"=>"text/html; charset=UTF-8" ); | |
| print render_html( $html, $title ); | |
| return DONE; | |
| } | |
| sub send_xml | |
| { | |
| my( $repository, $xmldata ) = @_; | |
| binmode( *STDOUT, ":utf8" ); | |
| $repository->send_http_header( "content_type"=>"text/xml; charset=UTF-8" ); | |
| # the dataobj XML generator now includes the XML header. This isn't ideal, but | |
| # it's a quick way to ensure we only send one. | |
| if( substr( $xmldata,0,5 ) ne "<?xml" ) | |
| { | |
| print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; | |
| } | |
| print $xmldata; | |
| return DONE; | |
| } | |
| sub send_plaintext | |
| { | |
| my( $repository, $content ) = @_; | |
| binmode( *STDOUT, ":utf8" ); | |
| $repository->send_http_header( "content_type"=>"text/plain; charset=UTF-8" ); | |
| if( defined $content ) | |
| { | |
| print $content; | |
| } | |
| return DONE; | |
| } | |
| sub allowed_methods | |
| { | |
| my( $repository, @methods ) = @_; | |
| my $method = $ENV{REQUEST_METHOD}; | |
| if( $method eq "OPTIONS" ) | |
| { | |
| EPrints::Apache::AnApache::header_out( | |
| $repository->get_request, | |
| "Content-Length" => 0 ); | |
| EPrints::Apache::AnApache::header_out( | |
| $repository->get_request, | |
| "Allow" => "OPTIONS,".join( ",", @methods ) ); | |
| EPrints::Apache::AnApache::send_http_header( $repository->get_request ); | |
| return 0; | |
| } | |
| foreach my $m ( @methods ) { return 1 if $m eq $method; } | |
| EPrints::Apache::AnApache::send_status_line( | |
| $repository->get_request, | |
| 501, | |
| "Method $method Not Implemented" ); | |
| EPrints::Apache::AnApache::send_http_header( $repository->get_request ); | |
| return 0; | |
| } | |
| sub allow_priv | |
| { | |
| my( $priv, $repository, $rights_object ) = @_; | |
| if( $priv =~ m/^eprint\// ) | |
| { | |
| my $status = $rights_object->get_value( "eprint_status" ); | |
| $priv =~ s/^eprint\//eprint\/$status\//; | |
| } | |
| return 1 if( $repository->allow_anybody( $priv ) ); | |
| my $r = $repository->get_request; | |
| $r->auth_type( "Basic" ); | |
| $r->auth_name( "EPrintsREST" ); | |
| my ($status, $password) = $r->get_basic_auth_pw; | |
| my $username = $r->user; | |
| my $real_username = $repository->valid_login( $username, $password ); | |
| if( !$real_username ) | |
| { | |
| $r->note_basic_auth_failure; | |
| EPrints::Apache::AnApache::send_status_line( $r, 401, "Auth Required" ); | |
| send_html( $repository, "<p>Auth Required</p>", "Auth Required" ); | |
| return 0; | |
| } | |
| my $user = $repository->user_by_username( $real_username ); | |
| if( !defined $user ) | |
| { | |
| EPrints::Apache::AnApache::send_status_line( $r, 403, "Forbidden" ); | |
| send_html( $repository, "<p>Forbidden</p>", "No such user" ); | |
| return 0; | |
| } | |
| if( !$user->allow( $priv, $rights_object ) ) | |
| { | |
| EPrints::Apache::AnApache::send_status_line( $r, 403, "Forbidden" ); | |
| send_html( $repository, "<p>Forbidden</p>", "Forbidden" ); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| # end of GET and PUT methods | |
| sub get_dataobj_xml | |
| { | |
| my( $repository, $object, $rights_object ) = @_; | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/get", $repository, $rights_object ); | |
| return send_xml( $repository, $object->export( "XML" ) ); | |
| } | |
| sub put_dataobj_xml | |
| { | |
| my( $repository, $object, $rights_object ) = @_; | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/put", $repository, $rights_object ); | |
| my $put = $repository->xml->create_element( "put", result=>"not-implemented" ); | |
| return send_xml( $repository, EPrints::XML::to_string( $put ) ); | |
| } | |
| sub get_field_txt | |
| { | |
| my( $repository, $object, $rights_object, $field ) = @_; | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/get", $repository, $rights_object ); | |
| my $v = $object->get_value( $field->get_name ); | |
| $v = "" if !defined $v; | |
| return send_plaintext( $repository, $v ); | |
| } | |
| sub get_field_xml | |
| { | |
| my( $repository, $object, $rights_object, $field ) = @_; | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/get", $repository, $rights_object ); | |
| my $v = $object->get_value( $field->get_name ); | |
| my $xml_dom = $field->to_xml( $v, show_empty=>1 ); | |
| my $xml_str = EPrints::XML::to_string( $xml_dom ); | |
| return send_xml( $repository, $xml_str."\n" ); | |
| } | |
| sub put_field_xml | |
| { | |
| my( $repository, $object, $rights_object, $field ) = @_; | |
| if( $field->get_name eq $object->dataset->get_key_field->get_name || $field->get_name eq "rev_number" ) | |
| { | |
| return 403; | |
| } | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/put", $repository, $rights_object ); | |
| my $data = join( "", <STDIN> ); | |
| if( $data eq "" ) | |
| { | |
| my $put = $repository->xml->create_element( "put", result=>"no-data" ); | |
| $put->appendChild( $repository->xml->create_text_node( "No data" ) ); | |
| return send_xml( $repository, EPrints::XML::to_string( $put ) ); | |
| } | |
| my $doc; | |
| eval { $doc = $repository->xml->parse_string( $data ); }; | |
| if( $@ ) | |
| { | |
| my $put = $repository->xml->create_element( "put", result=>"parse-error" ); | |
| $put->appendChild( $repository->xml->create_text_node( $@ ) ); | |
| return send_xml( $repository, EPrints::XML::to_string( $put ) ); | |
| } | |
| my $docel = $doc->getDocumentElement; | |
| my $fieldname = $docel->nodeName(); | |
| if( $fieldname ne $field->name ) | |
| { | |
| my $put = $repository->xml->create_element( "put", result=>"wrong-field" ); | |
| $put->appendChild( $repository->xml->create_text_node( "XML describes wrong field ($fieldname)" ) ); | |
| return send_xml( $repository, EPrints::XML::to_string( $put ) ); | |
| } | |
| my $value = $field->xml_to_epdata( $repository, $docel ); | |
| $object->set_value( $field->name, $value ); | |
| $object->commit; | |
| my $put = $repository->xml->create_element( "put", result=>"ok" ); | |
| return send_xml( $repository, EPrints::XML::to_string( $put ) ); | |
| } | |
| sub put_field_txt | |
| { | |
| my( $repository, $object, $rights_object, $field ) = @_; | |
| if( $field->get_name eq $object->dataset->get_key_field->get_name || $field->get_name eq "rev_number" ) | |
| { | |
| return 403; | |
| } | |
| return DONE unless allow_priv( $rights_object->dataset->confid."/rest/put", $repository, $rights_object ); | |
| my $data = join( "", <STDIN> ); | |
| $object->set_value( $field->get_name, $data ); | |
| $object->commit; | |
| return send_plaintext( $repository, "OK" ); | |
| } | |
| # end of GET and PUT methods | |
| 1; | |
| =head1 COPYRIGHT | |
| =for COPYRIGHT BEGIN | |
| Copyright 2000-2011 University of Southampton. | |
| =for COPYRIGHT END | |
| =for LICENSE BEGIN | |
| This file is part of EPrints L<http://www.eprints.org/>. | |
| EPrints is free software: you can redistribute it and/or modify it | |
| under the terms of the GNU Lesser General Public License as published | |
| by the Free Software Foundation, either version 3 of the License, or | |
| (at your option) any later version. | |
| EPrints is distributed in the hope that it will be useful, but WITHOUT | |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | |
| License for more details. | |
| You should have received a copy of the GNU Lesser General Public | |
| License along with EPrints. If not, see L<http://www.gnu.org/licenses/>. | |
| =for LICENSE END | |