cmis_server 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +51 -0
- data/app/assets/javascripts/cmis_server/application.js +13 -0
- data/app/assets/stylesheets/cmis_server/application.css +15 -0
- data/app/controllers/cmis_server/application_controller.rb +19 -0
- data/app/controllers/cmis_server/atom_pub/base_controller.rb +23 -0
- data/app/controllers/cmis_server/atom_pub/bulk_controller.rb +302 -0
- data/app/controllers/cmis_server/atom_pub/content_controller.rb +178 -0
- data/app/controllers/cmis_server/atom_pub/entries_controller.rb +41 -0
- data/app/controllers/cmis_server/atom_pub/folder_collection_controller.rb +70 -0
- data/app/controllers/cmis_server/atom_pub/query_controller.rb +138 -0
- data/app/controllers/cmis_server/atom_pub/repository_controller.rb +75 -0
- data/app/controllers/cmis_server/atom_pub/secondary_types_controller.rb +149 -0
- data/app/controllers/cmis_server/atom_pub/service_documents_controller.rb +34 -0
- data/app/controllers/cmis_server/atom_pub/types_controller.rb +229 -0
- data/app/controllers/cmis_server/atom_pub/uri_templates_controller.rb +58 -0
- data/app/controllers/concerns/cmis_server/atom_pub/repository_scopable.rb +18 -0
- data/app/helpers/cmis_server/application_helper.rb +31 -0
- data/app/models/cmis_server/application_record.rb +5 -0
- data/app/services/cmis_server/bulk_update_service.rb +69 -0
- data/app/services/cmis_server/content_stream_service.rb +75 -0
- data/app/services/cmis_server/discovery_service.rb +31 -0
- data/app/services/cmis_server/navigation_service.rb +43 -0
- data/app/services/cmis_server/object_service.rb +176 -0
- data/app/services/cmis_server/repository_service.rb +40 -0
- data/app/services/cmis_server/secondary_type_service.rb +120 -0
- data/app/services/cmis_server/type_management_service.rb +117 -0
- data/app/views/cmis_server/atom_pub/bulk_update_feed.atom.builder +47 -0
- data/app/views/cmis_server/atom_pub/entries/_cmis_document_links.atom_entry.builder +0 -0
- data/app/views/cmis_server/atom_pub/entries/_cmis_folder_links.atom_entry.builder +3 -0
- data/app/views/cmis_server/atom_pub/entries/_object_entry.atom_entry.builder +64 -0
- data/app/views/cmis_server/atom_pub/entries/_property.atom_entry.builder +8 -0
- data/app/views/cmis_server/atom_pub/entries/object_entry.atom_entry.builder +1 -0
- data/app/views/cmis_server/atom_pub/entries/type_entry.atom_entry.builder +59 -0
- data/app/views/cmis_server/atom_pub/feeds/_feed_entry.atom_feed.builder +1 -0
- data/app/views/cmis_server/atom_pub/feeds/feed.atom_feed.builder +30 -0
- data/app/views/cmis_server/atom_pub/service_documents/_uri_template.atom_service.builder +5 -0
- data/app/views/cmis_server/atom_pub/service_documents/_workspace.atom_service.builder +89 -0
- data/app/views/cmis_server/atom_pub/service_documents/service_document.atom_service.builder +1 -0
- data/app/views/cmis_server/atom_pub/shared/_collection.xml.builder +7 -0
- data/app/views/cmis_server/atom_pub/shared/atom_entry_layout.atom_entry.builder +0 -0
- data/app/views/cmis_server/atom_pub/type_entry.atom.builder +69 -0
- data/app/views/cmis_server/atom_pub/types_feed.atom.builder +91 -0
- data/app/views/layouts/cmis_server/application.atom_entry.builder +2 -0
- data/app/views/layouts/cmis_server/application.atom_feed.builder +3 -0
- data/app/views/layouts/cmis_server/application.atom_service.builder +10 -0
- data/app/views/layouts/cmis_server/application.html.erb +14 -0
- data/config/routes.rb +49 -0
- data/lib/cmis_server/atom_pub/entry_parser.rb +72 -0
- data/lib/cmis_server/base_objects/base_type.rb +128 -0
- data/lib/cmis_server/base_objects/document.rb +68 -0
- data/lib/cmis_server/base_objects/folder.rb +104 -0
- data/lib/cmis_server/base_objects/item.rb +25 -0
- data/lib/cmis_server/base_types.rb +123 -0
- data/lib/cmis_server/cmis_object.rb +190 -0
- data/lib/cmis_server/configuration.rb +43 -0
- data/lib/cmis_server/constants.rb +8 -0
- data/lib/cmis_server/content_stream.rb +52 -0
- data/lib/cmis_server/context.rb +11 -0
- data/lib/cmis_server/document_object.rb +12 -0
- data/lib/cmis_server/document_type.rb +67 -0
- data/lib/cmis_server/engine.rb +60 -0
- data/lib/cmis_server/exceptions.rb +185 -0
- data/lib/cmis_server/folder_object.rb +18 -0
- data/lib/cmis_server/folder_type.rb +34 -0
- data/lib/cmis_server/id.rb +18 -0
- data/lib/cmis_server/item_adapter.rb +26 -0
- data/lib/cmis_server/item_object.rb +15 -0
- data/lib/cmis_server/item_type.rb +7 -0
- data/lib/cmis_server/object_adapter.rb +79 -0
- data/lib/cmis_server/property.rb +29 -0
- data/lib/cmis_server/property_definition.rb +118 -0
- data/lib/cmis_server/query/parser.output +2250 -0
- data/lib/cmis_server/query/parser.racc +222 -0
- data/lib/cmis_server/query/parser.racc.rb +1039 -0
- data/lib/cmis_server/query/parser.rex +114 -0
- data/lib/cmis_server/query/parser.rex.rb +238 -0
- data/lib/cmis_server/query/statement.rb +395 -0
- data/lib/cmis_server/query.rb +2 -0
- data/lib/cmis_server/renderable_collection.rb +45 -0
- data/lib/cmis_server/renderable_object.rb +49 -0
- data/lib/cmis_server/repository.rb +91 -0
- data/lib/cmis_server/secondary_type.rb +11 -0
- data/lib/cmis_server/type.rb +62 -0
- data/lib/cmis_server/type_registry.rb +115 -0
- data/lib/cmis_server/version.rb +4 -0
- data/lib/cmis_server.rb +22 -0
- data/lib/tasks/cmis_server_tasks.rake +4 -0
- data/test/cmis_server_test.rb +7 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/navigation_test.rb +8 -0
- data/test/services/bulk_update_service_test.rb +121 -0
- data/test/services/content_stream_service_test.rb +176 -0
- data/test/services/secondary_type_service_test.rb +174 -0
- data/test/services/type_management_service_test.rb +146 -0
- data/test/test_helper.rb +16 -0
- metadata +326 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5a06882af4391857d952ae571fe62d9cecd8b189448b7a74da1f9aedc7d936fb
|
4
|
+
data.tar.gz: defe5fdf0356330dccaaa0d7819f9f5f0fccda34315ae9d056980eb5b9b10bb9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e322038687939ec2b099c76366dd2bc646af018e803f60b35861c2d71ca5ddff14ff275d009de7977be59c2185e12aaed72e6c1bce3b8fa4df1191ad8aa65680
|
7
|
+
data.tar.gz: b2f428f15608eb7aa583f42a00271ccac21f8b0b19ba8c9c82f3202113ab56622b249d0fd9799c7329e6699c32da8ec2c0e59c9e2e3fa23af6eb7054389483af
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Alexandre Narbonne
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'CmisServer'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
GENERATED_PARSER = 'lib/cmis_server/query/parser.racc.rb'
|
38
|
+
GENERATED_LEXER = 'lib/cmis_server/query/parser.rex.rb'
|
39
|
+
|
40
|
+
file GENERATED_LEXER => 'lib/cmis_server/query/parser.rex' do |t|
|
41
|
+
sh "rex -do #{t.name} #{t.prerequisites.first}"
|
42
|
+
end
|
43
|
+
|
44
|
+
file GENERATED_PARSER => 'lib/cmis_server/query/parser.racc' do |t|
|
45
|
+
sh "racc -t -g -v -o #{t.name} #{t.prerequisites.first}"
|
46
|
+
end
|
47
|
+
|
48
|
+
task :parser => [GENERATED_LEXER, GENERATED_PARSER]
|
49
|
+
|
50
|
+
# Make sure the parser's up-to-date when we test.
|
51
|
+
Rake::Task['test'].prerequisites << :parser
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CmisServer
|
2
|
+
class ApplicationController < ActionController::Base
|
3
|
+
protect_from_forgery with: :exception, unless: -> { request.format.symbol.in?([:atom_xml, :atom_feed, :atom_entry, :atom_service]) }
|
4
|
+
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
before_action :check_auth
|
7
|
+
|
8
|
+
def context
|
9
|
+
@context ||= Hash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_auth
|
13
|
+
#ToDo Proc to be provided in gem config by the final application
|
14
|
+
|
15
|
+
#auth_proc= Proc.new {|username,password| (resource=User.find_by(login: username))&&resource.valid_password?(password)&&(context.current_user=resource)}
|
16
|
+
context[:current_user] = authenticate_or_request_with_http_basic &CmisServer.configuration.http_basic_auth_procedure
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CmisServer
|
2
|
+
module AtomPub
|
3
|
+
class BaseController < CmisServer::ApplicationController
|
4
|
+
rescue_from StandardError, with: :render_standard_error
|
5
|
+
# skip_before_action :verify_authenticity_token # Déjà défini dans ApplicationController
|
6
|
+
|
7
|
+
def raw_post_body
|
8
|
+
@raw_body ||= request.raw_post
|
9
|
+
end
|
10
|
+
|
11
|
+
def request_entry_parser
|
12
|
+
@r_entry_parser ||= CmisServer::EntryParser.new(raw_post_body)
|
13
|
+
end
|
14
|
+
|
15
|
+
def render_standard_error(error)
|
16
|
+
cmis_error = error.is_a?(CmisServer::CmisStandardError) ? error : CmisServer::RuntimeError.new(trigger: error)
|
17
|
+
cmis_error.log(Rails.logger)
|
18
|
+
|
19
|
+
render xml: cmis_error.to_xml, content_type: 'application/xml', status: cmis_error.http_status_code
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
module CmisServer
|
2
|
+
module AtomPub
|
3
|
+
class BulkController < BaseController
|
4
|
+
include CmisServer::AtomPub::RepositoryScopable
|
5
|
+
|
6
|
+
before_action :check_repository_capabilities
|
7
|
+
|
8
|
+
# POST /bulk - bulkUpdateProperties
|
9
|
+
def update_properties
|
10
|
+
bulk_data = parse_bulk_data_from_request
|
11
|
+
validate_bulk_data(bulk_data)
|
12
|
+
|
13
|
+
# Utiliser le service dédié pour la mise à jour en masse
|
14
|
+
bulk_service = CmisServer::BulkUpdateService.new(context: context)
|
15
|
+
results = bulk_service.bulk_update(bulk_data[:object_ids], bulk_data[:properties])
|
16
|
+
|
17
|
+
respond_to do |format|
|
18
|
+
format.atom_feed { render "cmis_server/atom_pub/bulk_update_feed", locals: { results: results } }
|
19
|
+
format.json { render json: format_bulk_results_for_json(results) }
|
20
|
+
end
|
21
|
+
rescue CmisServer::BulkUpdateService::CapabilityError => e
|
22
|
+
render_error(CmisServer::NotSupported.new(e.message))
|
23
|
+
rescue => e
|
24
|
+
render_error(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
# POST /bulk/delete - bulkDeleteProperties (nouvelle fonctionnalité CMIS 1.1)
|
28
|
+
def delete_properties
|
29
|
+
bulk_data = parse_bulk_delete_from_request
|
30
|
+
validate_bulk_delete_data(bulk_data)
|
31
|
+
|
32
|
+
results = delete_properties_from_objects(bulk_data)
|
33
|
+
|
34
|
+
respond_to do |format|
|
35
|
+
format.atom_feed { render "cmis_server/atom_pub/bulk_delete_feed", locals: { results: results } }
|
36
|
+
format.json { render json: format_bulk_results_for_json(results) }
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
render_error(e)
|
40
|
+
end
|
41
|
+
|
42
|
+
# POST /bulk/move - bulkMoveObjects (extension utile)
|
43
|
+
def move_objects
|
44
|
+
bulk_data = parse_bulk_move_from_request
|
45
|
+
validate_bulk_move_data(bulk_data)
|
46
|
+
|
47
|
+
results = move_objects_bulk(bulk_data)
|
48
|
+
|
49
|
+
respond_to do |format|
|
50
|
+
format.atom_feed { render "cmis_server/atom_pub/bulk_move_feed", locals: { results: results } }
|
51
|
+
format.json { render json: format_bulk_results_for_json(results) }
|
52
|
+
end
|
53
|
+
rescue => e
|
54
|
+
render_error(e)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def check_repository_capabilities
|
60
|
+
repository = repository_from_request
|
61
|
+
|
62
|
+
case action_name
|
63
|
+
when 'update_properties'
|
64
|
+
unless repository.capabilities[:bulk_update]
|
65
|
+
raise CmisServer::NotSupported.new("Repository does not support bulk update operations")
|
66
|
+
end
|
67
|
+
when 'delete_properties'
|
68
|
+
unless repository.capabilities[:bulk_delete_properties]
|
69
|
+
raise CmisServer::NotSupported.new("Repository does not support bulk delete properties operations")
|
70
|
+
end
|
71
|
+
when 'move_objects'
|
72
|
+
unless repository.capabilities[:bulk_move]
|
73
|
+
raise CmisServer::NotSupported.new("Repository does not support bulk move operations")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_bulk_data_from_request
|
79
|
+
if supports_browser_binding?
|
80
|
+
parse_browser_binding_bulk_request
|
81
|
+
elsif request.content_type =~ /application\/atom\+xml/
|
82
|
+
parse_atom_pub_bulk_request
|
83
|
+
else
|
84
|
+
raise CmisServer::InvalidOperation.new("Unsupported content type: #{request.content_type}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_atom_pub_bulk_request
|
89
|
+
doc = Nokogiri::XML(request.body.read)
|
90
|
+
doc.remove_namespaces! # Simplifier le parsing
|
91
|
+
|
92
|
+
object_ids = doc.xpath("//objectId").map(&:text)
|
93
|
+
properties = parse_properties_from_xml(doc)
|
94
|
+
|
95
|
+
{ object_ids: object_ids, properties: properties }
|
96
|
+
end
|
97
|
+
|
98
|
+
def parse_browser_binding_bulk_request
|
99
|
+
json_data = JSON.parse(request.body.read)
|
100
|
+
|
101
|
+
object_ids = json_data["objectIds"] || []
|
102
|
+
properties = json_data["properties"] || {}
|
103
|
+
|
104
|
+
{ object_ids: object_ids, properties: properties }
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse_properties_from_xml(doc)
|
108
|
+
properties = {}
|
109
|
+
|
110
|
+
doc.xpath("//properties/*").each do |prop_node|
|
111
|
+
prop_id = prop_node.attributes["propertyDefinitionId"]&.value
|
112
|
+
next unless prop_id
|
113
|
+
|
114
|
+
prop_type = prop_node.name.split(":").last
|
115
|
+
value_nodes = prop_node.xpath(".//value")
|
116
|
+
|
117
|
+
if value_nodes.empty?
|
118
|
+
properties[prop_id] = nil
|
119
|
+
elsif value_nodes.length == 1
|
120
|
+
properties[prop_id] = convert_property_value(value_nodes.first.text, prop_type)
|
121
|
+
else
|
122
|
+
properties[prop_id] = value_nodes.map { |n| convert_property_value(n.text, prop_type) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
properties
|
127
|
+
end
|
128
|
+
|
129
|
+
def validate_bulk_data(bulk_data)
|
130
|
+
if bulk_data[:object_ids].nil? || bulk_data[:object_ids].empty?
|
131
|
+
raise CmisServer::InvalidOperation.new("No object IDs specified for bulk update")
|
132
|
+
end
|
133
|
+
|
134
|
+
if bulk_data[:properties].nil? || bulk_data[:properties].empty?
|
135
|
+
raise CmisServer::InvalidOperation.new("No properties specified for bulk update")
|
136
|
+
end
|
137
|
+
|
138
|
+
# Valider que tous les object_ids sont des chaînes non vides
|
139
|
+
bulk_data[:object_ids].each do |id|
|
140
|
+
if id.nil? || id.to_s.strip.empty?
|
141
|
+
raise CmisServer::InvalidOperation.new("Invalid object ID: #{id}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_bulk_delete_from_request
|
147
|
+
if supports_browser_binding?
|
148
|
+
json_data = JSON.parse(request.body.read)
|
149
|
+
{
|
150
|
+
object_ids: json_data["objectIds"] || [],
|
151
|
+
property_ids: json_data["propertyIds"] || []
|
152
|
+
}
|
153
|
+
else
|
154
|
+
doc = Nokogiri::XML(request.body.read)
|
155
|
+
doc.remove_namespaces!
|
156
|
+
|
157
|
+
object_ids = doc.xpath("//objectId").map(&:text)
|
158
|
+
property_ids = doc.xpath("//propertyId").map(&:text)
|
159
|
+
|
160
|
+
{ object_ids: object_ids, property_ids: property_ids }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def validate_bulk_delete_data(bulk_data)
|
165
|
+
if bulk_data[:object_ids].empty?
|
166
|
+
raise CmisServer::InvalidOperation.new("No object IDs specified for bulk delete")
|
167
|
+
end
|
168
|
+
|
169
|
+
if bulk_data[:property_ids].empty?
|
170
|
+
raise CmisServer::InvalidOperation.new("No property IDs specified for bulk delete")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def parse_bulk_move_from_request
|
175
|
+
if supports_browser_binding?
|
176
|
+
json_data = JSON.parse(request.body.read)
|
177
|
+
{
|
178
|
+
object_ids: json_data["objectIds"] || [],
|
179
|
+
target_folder_id: json_data["targetFolderId"],
|
180
|
+
source_folder_id: json_data["sourceFolderId"]
|
181
|
+
}
|
182
|
+
else
|
183
|
+
doc = Nokogiri::XML(request.body.read)
|
184
|
+
doc.remove_namespaces!
|
185
|
+
|
186
|
+
object_ids = doc.xpath("//objectId").map(&:text)
|
187
|
+
target_folder_id = doc.at_xpath("//targetFolderId")&.text
|
188
|
+
source_folder_id = doc.at_xpath("//sourceFolderId")&.text
|
189
|
+
|
190
|
+
{ object_ids: object_ids, target_folder_id: target_folder_id, source_folder_id: source_folder_id }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def validate_bulk_move_data(bulk_data)
|
195
|
+
if bulk_data[:object_ids].empty?
|
196
|
+
raise CmisServer::InvalidOperation.new("No object IDs specified for bulk move")
|
197
|
+
end
|
198
|
+
|
199
|
+
if bulk_data[:target_folder_id].nil? || bulk_data[:target_folder_id].strip.empty?
|
200
|
+
raise CmisServer::InvalidOperation.new("Target folder ID is required for bulk move")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def delete_properties_from_objects(bulk_data)
|
205
|
+
results = []
|
206
|
+
|
207
|
+
bulk_data[:object_ids].each do |object_id|
|
208
|
+
begin
|
209
|
+
object = find_object(object_id)
|
210
|
+
|
211
|
+
bulk_data[:property_ids].each do |property_id|
|
212
|
+
object.properties.delete(property_id)
|
213
|
+
end
|
214
|
+
|
215
|
+
object.save
|
216
|
+
results << { id: object_id, status: "success" }
|
217
|
+
rescue => e
|
218
|
+
results << { id: object_id, status: "error", message: e.message }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
results
|
223
|
+
end
|
224
|
+
|
225
|
+
def move_objects_bulk(bulk_data)
|
226
|
+
results = []
|
227
|
+
|
228
|
+
bulk_data[:object_ids].each do |object_id|
|
229
|
+
begin
|
230
|
+
object = find_object(object_id)
|
231
|
+
target_folder = find_object(bulk_data[:target_folder_id])
|
232
|
+
|
233
|
+
object.move_to(target_folder)
|
234
|
+
results << { id: object_id, status: "success" }
|
235
|
+
rescue => e
|
236
|
+
results << { id: object_id, status: "error", message: e.message }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
results
|
241
|
+
end
|
242
|
+
|
243
|
+
def format_bulk_results_for_json(results)
|
244
|
+
{
|
245
|
+
results: results,
|
246
|
+
totalCount: results.length,
|
247
|
+
successCount: results.count { |r| r[:status] == "success" },
|
248
|
+
errorCount: results.count { |r| r[:status] == "error" }
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
def supports_browser_binding?
|
253
|
+
request.content_type.include?('application/json') ||
|
254
|
+
params[:cmisbrowserbinding] == 'true'
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
def update_objects(bulk_data)
|
259
|
+
# Cette méthode est maintenant remplacée par l'utilisation du BulkUpdateService
|
260
|
+
# Conservée pour compatibilité si nécessaire
|
261
|
+
bulk_service = CmisServer::BulkUpdateService.new(context: context)
|
262
|
+
results = bulk_service.bulk_update(bulk_data[:object_ids], bulk_data[:properties])
|
263
|
+
|
264
|
+
# Formatter pour correspondre à l'ancien format attendu
|
265
|
+
formatted_results = []
|
266
|
+
results[:succeeded].each { |id| formatted_results << { id: id, status: "success" } }
|
267
|
+
results[:failed].each { |failure| formatted_results << { id: failure[:id], status: "error", message: failure[:message] } }
|
268
|
+
|
269
|
+
formatted_results
|
270
|
+
end
|
271
|
+
|
272
|
+
def find_object(object_id)
|
273
|
+
# Cette méthode doit être implémentée selon votre système de stockage
|
274
|
+
# Elle devrait retourner l'objet CMIS correspondant à l'ID ou lever une exception
|
275
|
+
object = repository_from_request.get_object(object_id)
|
276
|
+
raise CmisServer::ObjectNotFound.new unless object
|
277
|
+
object
|
278
|
+
end
|
279
|
+
|
280
|
+
def update_object_properties(object, properties)
|
281
|
+
# Cette méthode doit être implémentée selon votre système de stockage
|
282
|
+
# Elle devrait mettre à jour les propriétés de l'objet
|
283
|
+
object.update_properties(properties)
|
284
|
+
end
|
285
|
+
|
286
|
+
def convert_property_value(value, type)
|
287
|
+
case type
|
288
|
+
when "propertyInteger"
|
289
|
+
value.to_i
|
290
|
+
when "propertyDecimal", "propertyDouble"
|
291
|
+
value.to_f
|
292
|
+
when "propertyBoolean"
|
293
|
+
value.downcase == "true"
|
294
|
+
when "propertyDateTime"
|
295
|
+
DateTime.parse(value)
|
296
|
+
else
|
297
|
+
value
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module CmisServer
|
2
|
+
module AtomPub
|
3
|
+
class ContentController < CmisServer::AtomPub::BaseController
|
4
|
+
|
5
|
+
include CmisServer::AtomPub::RepositoryScopable
|
6
|
+
before_action :check_appendable_capability, only: [:append]
|
7
|
+
|
8
|
+
def show
|
9
|
+
content_id=params[:id]
|
10
|
+
doc_id =content_id[3..-1]
|
11
|
+
document =CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
|
12
|
+
if document&&(cs=document.content_stream)
|
13
|
+
send_data(cs.content,
|
14
|
+
filename: cs.filename,
|
15
|
+
type: cs.media_type
|
16
|
+
)
|
17
|
+
else
|
18
|
+
head :not_found
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# POST /content/{id}/append
|
23
|
+
# Implémentation de appendContentStream pour CMIS 1.1
|
24
|
+
def append
|
25
|
+
content_id = params[:id]
|
26
|
+
doc_id = content_id[3..-1]
|
27
|
+
document = CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
|
28
|
+
|
29
|
+
raise CmisServer::ObjectNotFound.new unless document
|
30
|
+
|
31
|
+
# Vérifier que l'objet est un document
|
32
|
+
raise CmisServer::InvalidOperation.new("Object is not a document") unless document.is_a?(CmisServer::DocumentObject)
|
33
|
+
|
34
|
+
# Vérifier que le document a déjà un content stream
|
35
|
+
raise CmisServer::InvalidOperation.new("Document has no content stream") unless document.content_stream
|
36
|
+
|
37
|
+
# Utiliser le service dédié pour l'opération
|
38
|
+
content_service = CmisServer::ContentStreamService.new(context: context)
|
39
|
+
new_content = parse_content_from_request
|
40
|
+
|
41
|
+
updated_document = content_service.append_content_stream(document, new_content)
|
42
|
+
|
43
|
+
respond_to do |format|
|
44
|
+
format.atom_entry { render "cmis_server/atom_pub/document_entry", locals: { document: updated_document } }
|
45
|
+
format.json { render json: updated_document.to_json }
|
46
|
+
end
|
47
|
+
rescue => e
|
48
|
+
render_error(e)
|
49
|
+
end
|
50
|
+
|
51
|
+
# PUT /content/{id} - setContentStream
|
52
|
+
def set_content_stream
|
53
|
+
content_id = params[:id]
|
54
|
+
doc_id = content_id[3..-1]
|
55
|
+
document = CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
|
56
|
+
|
57
|
+
raise CmisServer::ObjectNotFound.new unless document
|
58
|
+
|
59
|
+
overwrite_flag = params[:overwriteFlag] != 'false'
|
60
|
+
new_content = parse_content_from_request
|
61
|
+
|
62
|
+
content_service = CmisServer::ContentStreamService.new(context: context)
|
63
|
+
updated_document = content_service.set_content_stream(document, new_content, overwrite_flag)
|
64
|
+
|
65
|
+
respond_to do |format|
|
66
|
+
format.atom_entry { render "cmis_server/atom_pub/document_entry", locals: { document: updated_document } }
|
67
|
+
format.json { render json: updated_document.to_json }
|
68
|
+
end
|
69
|
+
rescue => e
|
70
|
+
render_error(e)
|
71
|
+
end
|
72
|
+
|
73
|
+
# DELETE /content/{id} - deleteContentStream
|
74
|
+
def delete_content_stream
|
75
|
+
content_id = params[:id]
|
76
|
+
doc_id = content_id[3..-1]
|
77
|
+
document = CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
|
78
|
+
|
79
|
+
raise CmisServer::ObjectNotFound.new unless document
|
80
|
+
|
81
|
+
content_service = CmisServer::ContentStreamService.new(context: context)
|
82
|
+
updated_document = content_service.delete_content_stream(document)
|
83
|
+
|
84
|
+
respond_to do |format|
|
85
|
+
format.atom_entry { render "cmis_server/atom_pub/document_entry", locals: { document: updated_document } }
|
86
|
+
format.json { render json: updated_document.to_json }
|
87
|
+
end
|
88
|
+
rescue => e
|
89
|
+
render_error(e)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def check_appendable_capability
|
95
|
+
repository = repository_from_request
|
96
|
+
|
97
|
+
unless repository.capabilities[:append_content_stream]
|
98
|
+
raise CmisServer::NotSupported.new(
|
99
|
+
"Repository does not support appending to content streams"
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_content_from_request
|
105
|
+
# Détection améliorée du type de contenu pour CMIS 1.1
|
106
|
+
if request.content_type =~ /multipart\/form-data/
|
107
|
+
# Contenu provenant d'un formulaire multipart
|
108
|
+
file = params[:file]
|
109
|
+
|
110
|
+
validate_content_stream(file)
|
111
|
+
|
112
|
+
CmisServer::ContentStream.new(
|
113
|
+
id: SecureRandom.uuid,
|
114
|
+
base64: Base64.strict_encode64(file.read),
|
115
|
+
media_type: file.content_type,
|
116
|
+
filename: file.original_filename,
|
117
|
+
length: file.size
|
118
|
+
)
|
119
|
+
else
|
120
|
+
# Contenu brut avec validation
|
121
|
+
body_content = request.body.read
|
122
|
+
validate_raw_content(body_content)
|
123
|
+
|
124
|
+
CmisServer::ContentStream.new(
|
125
|
+
id: SecureRandom.uuid,
|
126
|
+
base64: Base64.strict_encode64(body_content),
|
127
|
+
media_type: request.content_type || 'application/octet-stream',
|
128
|
+
filename: params[:filename] || detect_filename_from_headers || "unnamed",
|
129
|
+
length: body_content.length
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def validate_content_stream(file)
|
135
|
+
# Validation des limites de taille et types MIME
|
136
|
+
max_size = repository_from_request.capabilities[:max_content_size_kb] || 100_000 # 100MB par défaut
|
137
|
+
|
138
|
+
if file.size > max_size * 1024
|
139
|
+
raise CmisServer::StreamNotSupported.new("Content size exceeds maximum allowed size")
|
140
|
+
end
|
141
|
+
|
142
|
+
allowed_mime_types = repository_from_request.capabilities[:allowed_mime_types]
|
143
|
+
if allowed_mime_types && !allowed_mime_types.include?(file.content_type)
|
144
|
+
raise CmisServer::StreamNotSupported.new("MIME type '#{file.content_type}' not allowed")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def validate_raw_content(content)
|
149
|
+
max_size = repository_from_request.capabilities[:max_content_size_kb] || 100_000
|
150
|
+
|
151
|
+
if content.length > max_size * 1024
|
152
|
+
raise CmisServer::StreamNotSupported.new("Content size exceeds maximum allowed size")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def detect_filename_from_headers
|
157
|
+
# Essayer d'extraire le nom de fichier des headers HTTP
|
158
|
+
content_disposition = request.headers['Content-Disposition']
|
159
|
+
return nil unless content_disposition
|
160
|
+
|
161
|
+
match = content_disposition.match(/filename=["']?([^"'\s;]+)["']?/)
|
162
|
+
match[1] if match
|
163
|
+
end
|
164
|
+
|
165
|
+
def merge_content_streams(existing, new_content)
|
166
|
+
# Créer un nouveau ContentStream avec le contenu fusionné
|
167
|
+
merged_content = CmisServer::ContentStream.new(
|
168
|
+
id: existing.id,
|
169
|
+
base64: Base64.strict_encode64(existing.content + new_content.content),
|
170
|
+
media_type: existing.media_type,
|
171
|
+
filename: existing.filename
|
172
|
+
)
|
173
|
+
|
174
|
+
merged_content
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CmisServer
|
2
|
+
module AtomPub
|
3
|
+
class EntriesController < CmisServer::AtomPub::BaseController
|
4
|
+
|
5
|
+
include CmisServer::AtomPub::RepositoryScopable
|
6
|
+
|
7
|
+
def show
|
8
|
+
@object=object_service.get_object(id, with_object: true, filter: filter)
|
9
|
+
render 'cmis_server/atom_pub/entries/object_entry', formats: :atom_entry
|
10
|
+
end
|
11
|
+
|
12
|
+
def update
|
13
|
+
@object=object_service.update_properties(id, request_entry_parser.object_properties, with_object: true)
|
14
|
+
render 'cmis_server/atom_pub/entries/object_entry', formats: :atom_entry, status: 200
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def filter
|
24
|
+
params[:filter].to_s.split(',')
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
params.require(:id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def object_service
|
32
|
+
ObjectService.new(@repository, context)
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_format
|
36
|
+
request.format = :atom_entry
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|