Skip to content

Commit 301be5d

Browse files
jendruskAlessio Fabiani
and
Alessio Fabiani
authored
Added settings management (#25)
* Services before refactor * Added services settings * Disabled create_service function * Part of changes - faced problem with geoserver - potential issue * Added setting self and master passwords * Settings simple implementation * Settings Subclass implementation - seems working needs tests * Settings full implementation * - Test fixes Co-authored-by: Alessio Fabiani <alessio.fabiani@geo-solutions.it>
1 parent ff8b645 commit 301be5d

File tree

4 files changed

+486
-17
lines changed

4 files changed

+486
-17
lines changed

src/geoserver/catalog.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from geoserver.layergroup import LayerGroup, UnsavedLayerGroup
2828
from geoserver.workspace import workspace_from_index, Workspace
2929
from geoserver.security import user_from_index
30+
from geoserver.settings import GlobalSettings
3031
import os
3132
import re
3233
import base64
@@ -144,7 +145,6 @@ def setup_connection(self, retries=3, backoff_factor=0.9):
144145
self.client.mount(f"{parsed_url.scheme}://", HTTPAdapter(max_retries=retry))
145146

146147
def http_request(self, url, data=None, method='get', headers={}, files=None):
147-
148148
req_method = getattr(self.client, method.lower())
149149

150150
if self.access_token:
@@ -289,6 +289,7 @@ def save(self, obj, content_type="application/xml"):
289289
netloc = urlparse(self.service_url).netloc
290290
rest_url = href._replace(netloc=netloc).geturl()
291291
data = obj.message()
292+
print(data)
292293

293294
headers = {
294295
"Content-type": content_type,
@@ -1384,7 +1385,6 @@ def set_master_pwd(self, new_pwd):
13841385
"<newMasterPassword>{new_pwd}</newMasterPassword>"
13851386
"</masterPassword>").format(old_pwd=old_pwd, new_pwd=new_pwd)
13861387
resp = self.http_request(url, method="put", data=body, headers=headers)
1387-
13881388
if resp.status_code == 200:
13891389
res = new_pwd
13901390
self.reload()
@@ -1408,3 +1408,6 @@ def set_my_pwd(self, new_pwd):
14081408
else:
14091409
raise FailedRequestError(resp.content)
14101410
return res
1411+
1412+
def get_global_settings(self):
1413+
return GlobalSettings(self)

src/geoserver/settings.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# -*- coding: utf-8 -*-
2+
#########################################################################
3+
#
4+
# Copyright 2019, GeoSolutions Sas.
5+
# Jendrusk also was here
6+
# All rights reserved.
7+
#
8+
# This source code is licensed under the MIT license found in the
9+
# LICENSE.txt file in the root directory of this source tree.
10+
#
11+
#########################################################################
12+
try:
13+
from urllib.parse import urljoin
14+
except BaseException:
15+
from urlparse import urljoin
16+
17+
from geoserver.support import (
18+
ResourceInfo, StaticResourceInfo,
19+
xml_property,
20+
read_bool, read_float, read_int, read_string,
21+
write_bool, write_float, write_int, write_string)
22+
23+
24+
def write_subclass(sbc):
25+
def write(builder, sbc):
26+
sbc.serialize_all(builder)
27+
return write
28+
29+
30+
class Contact(StaticResourceInfo):
31+
resource_type = "contact"
32+
33+
def __init__(self, dom):
34+
super(Contact, self).__init__()
35+
self.dom = dom
36+
37+
addressCity = xml_property("addressCity", read_string)
38+
addressCountry = xml_property("addressCountry", read_string)
39+
addressType = xml_property("addressType", read_string)
40+
contactEmail = xml_property("contactEmail", read_string)
41+
contactOrganization = xml_property("contactOrganization", read_string)
42+
contactPerson = xml_property("contactPerson", read_string)
43+
contactPosition = xml_property("contactPosition", read_string)
44+
45+
writers = {
46+
"addressCity": write_string("addressCity"),
47+
"addressCountry": write_string("addressCountry"),
48+
"addressType": write_string("addressType"),
49+
"contactEmail": write_string("contactEmail"),
50+
"contactOrganization": write_string("contactOrganization"),
51+
"contactPerson": write_string("contactPerson"),
52+
"contactPosition": write_string("contactPosition")
53+
}
54+
55+
56+
class Settings(StaticResourceInfo):
57+
resource_type = "settings"
58+
59+
def __init__(self, dom):
60+
super(Settings, self).__init__()
61+
self.dom = dom
62+
63+
id = xml_property("id", read_string)
64+
contact = xml_property("contact", Contact)
65+
charset = xml_property("charset", read_string)
66+
numDecimals = xml_property("numDecimals", read_int)
67+
onlineResource = xml_property("onlineResource", read_string)
68+
verbose = xml_property("verbose", read_bool)
69+
verboseExceptions = xml_property("verboseExceptions", read_bool)
70+
localWorkspaceIncludesPrefix = xml_property("localWorkspaceIncludesPrefix", read_bool)
71+
72+
writers = {
73+
"id": write_string("id"),
74+
"contact": write_subclass("contact"),
75+
"charset": write_string("charset"),
76+
"numDecimals": write_int("numDecimals"),
77+
"onlineResource": write_string("onlineResource"),
78+
"verbose": write_bool("verbose"),
79+
"verboseExceptions": write_bool("verboseExceptions"),
80+
"localWorkspaceIncludesPrefix": write_bool("localWorkspaceIncludesPrefix"),
81+
}
82+
83+
84+
class Jai(StaticResourceInfo):
85+
86+
def __init__(self, dom):
87+
super(Jai, self).__init__()
88+
self.dom = dom
89+
90+
resource_type = "jai"
91+
92+
allowInterpolation = xml_property("allowInterpolation", read_bool)
93+
recycling = xml_property("recycling", read_bool)
94+
tilePriority = xml_property("tilePriority", read_int)
95+
tileThreads = xml_property("tileThreads", read_int)
96+
memoryCapacity = xml_property("memoryCapacity", read_float)
97+
memoryThreshold = xml_property("memoryThreshold", read_float)
98+
imageIOCache = xml_property("imageIOCache", read_bool)
99+
pngAcceleration = xml_property("pngAcceleration", read_bool)
100+
jpegAcceleration = xml_property("jpegAcceleration", read_bool)
101+
allowNativeMosaic = xml_property("allowNativeMosaic", read_bool)
102+
allowNativeWarp = xml_property("allowNativeWarp", read_bool)
103+
104+
writers = {
105+
"allowInterpolation": write_bool("allowInterpolation"),
106+
"recycling": write_bool("recycling"),
107+
"tilePriority": write_int("tilePriority"),
108+
"tileThreads": write_int("tileThreads"),
109+
"memoryCapacity": write_float("memoryCapacity"),
110+
"memoryThreshold": write_float("memoryThreshold"),
111+
"imageIOCache": write_bool("imageIOCache"),
112+
"pngAcceleration": write_bool("pngAcceleration"),
113+
"jpegAcceleration": write_bool("jpegAcceleration"),
114+
"allowNativeMosaic": write_bool("allowNativeMosaic"),
115+
"allowNativeWarp": write_bool("allowNativeWarp")}
116+
117+
118+
class CoverageAccess(StaticResourceInfo):
119+
120+
def __init__(self, dom):
121+
super(CoverageAccess, self).__init__()
122+
self.dom = dom
123+
124+
resource_type = "coverageAccess"
125+
126+
maxPoolSize = xml_property("maxPoolSize", read_int)
127+
corePoolSize = xml_property("corePoolSize", read_int)
128+
keepAliveTime = xml_property("keepAliveTime", read_int)
129+
queueType = xml_property("queueType", read_string)
130+
imageIOCacheThreshold = xml_property("imageIOCacheThreshold", read_int)
131+
132+
writers = {
133+
"maxPoolSize": write_int("maxPoolSize"),
134+
"corePoolSize": write_int("corePoolSize"),
135+
"keepAliveTime": write_int("keepAliveTime"),
136+
"queueType": write_string("queueType"),
137+
"imageIOCacheThreshold": write_int("imageIOCacheThreshold")
138+
}
139+
140+
141+
class GlobalSettings(ResourceInfo):
142+
resource_type = "global"
143+
save_method = "put"
144+
145+
def __init__(self, catalog):
146+
super(GlobalSettings, self).__init__()
147+
self._catalog = catalog
148+
149+
@property
150+
def catalog(self):
151+
return self._catalog
152+
153+
@property
154+
def href(self):
155+
return urljoin(
156+
f"{self.catalog.service_url}/",
157+
"settings"
158+
)
159+
160+
settings = xml_property("settings", Settings)
161+
jai = xml_property("jai", Jai)
162+
coverageAccess = xml_property("coverageAccess", CoverageAccess)
163+
updateSequence = xml_property("updateSequence", lambda x: int(x.text))
164+
featureTypeCacheSize = xml_property("featureTypeCacheSize", lambda x: int(x.text))
165+
globalServices = xml_property("globalServices", lambda x: x.text.lower() == 'true')
166+
xmlPostRequestLogBufferSize = xml_property("xmlPostRequestLogBufferSize", lambda x: int(x.text))
167+
168+
writers = {
169+
'settings': write_subclass("settings"),
170+
'jai': write_subclass("jai"),
171+
'coverageAccess': write_subclass("coverageAccess"),
172+
'featureTypeCacheSize': write_int("featureTypeCacheSize"),
173+
'globalServices': write_bool("globalServices"),
174+
'xmlPostRequestLogBufferSize': write_int("xmlPostRequestLogBufferSize")
175+
}
176+
177+
def __repr__(self):
178+
return f"settings @ {self.href}"

src/geoserver/support.py

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ def clean_segment(segment):
7373

7474
def xml_property(path, converter=lambda x: x.text, default=None):
7575
def getter(self):
76+
if hasattr(self, f"_{path}"):
77+
return getattr(self, f"_{path}")
7678
try:
7779
if path in self.dirty:
7880
return self.dirty[path]
@@ -81,13 +83,17 @@ def getter(self):
8183
self.fetch()
8284
node = self.dom.find(path)
8385
if node is not None:
84-
return converter(self.dom.find(path))
86+
res = converter(self.dom.find(path))
87+
if issubclass(type(res), StaticResourceInfo):
88+
setattr(self, f"_{path}", res)
89+
return res
8590
return default
8691
except Exception as e:
8792
raise AttributeError(e)
8893

8994
def setter(self, value):
9095
self.dirty[path] = value
96+
x = 1
9197

9298
def delete(self):
9399
self.dirty[path] = None
@@ -127,6 +133,10 @@ def key_value_pairs(node):
127133
return dict((entry.attrib['key'], entry.text) for entry in node.findall("entry"))
128134

129135

136+
def read_string(node):
137+
return node.text
138+
139+
130140
def write_string(name):
131141
def write(builder, value):
132142
builder.start(name, dict())
@@ -136,6 +146,14 @@ def write(builder, value):
136146
return write
137147

138148

149+
def read_bool(node):
150+
text = node.text
151+
if text:
152+
return text == "true"
153+
else:
154+
return None
155+
156+
139157
def write_bool(name):
140158
def write(builder, b):
141159
builder.start(name, dict())
@@ -144,6 +162,15 @@ def write(builder, b):
144162
return write
145163

146164

165+
def read_int(node):
166+
text = node.text
167+
try:
168+
res = int(text)
169+
return res
170+
except Exception as e:
171+
raise ValueError(e)
172+
173+
147174
def write_int(name):
148175
def write(builder, b):
149176
builder.start(name, dict())
@@ -152,6 +179,23 @@ def write(builder, b):
152179
return write
153180

154181

182+
def read_float(node):
183+
text = node.text
184+
try:
185+
res = float(text)
186+
return res
187+
except Exception as e:
188+
raise ValueError(e)
189+
190+
191+
def write_float(name):
192+
def write(builder, b):
193+
builder.start(name, dict())
194+
builder.data(str(b))
195+
builder.end(name)
196+
return write
197+
198+
155199
def write_bbox(name):
156200
def write(builder, b):
157201
builder.start(name, dict())
@@ -205,21 +249,20 @@ def write(builder, metadata):
205249
return write
206250

207251

208-
class ResourceInfo(object):
252+
class StaticResourceInfo(object):
209253

210254
def __init__(self):
255+
self.write_all = True
211256
self.dom = None
212257
self.dirty = dict()
213258

214-
def fetch(self):
215-
self.dom = self.catalog.get_xml(self.href)
216-
217-
def clear(self):
218-
self.dirty = dict()
219-
220-
def refresh(self):
221-
self.clear()
222-
self.fetch()
259+
def dirty_all(self):
260+
for key in self.writers.keys():
261+
attr = getattr(self, key)
262+
if issubclass(type(attr), StaticResourceInfo):
263+
attr.dirty_all()
264+
else:
265+
self.dirty[key] = attr
223266

224267
def serialize(self, builder):
225268
# GeoServer will disable the resource if we omit the <enabled> tag,
@@ -231,18 +274,52 @@ def serialize(self, builder):
231274
self.dirty['advertised'] = self.advertised
232275

233276
for k, writer in self.writers.items():
234-
if k in self.dirty:
235-
writer(builder, self.dirty[k])
236277

237-
def message(self):
238-
builder = TreeBuilder()
278+
attr = getattr(self, k)
279+
280+
if issubclass(type(attr), StaticResourceInfo) and attr.dirty:
281+
attr.serialize_all(builder)
282+
else:
283+
if k in self.dirty or self.write_all:
284+
val = self.dirty[k] if self.dirty.get(k) else attr
285+
writer(builder, val)
286+
287+
def serialize_all(self, builder):
239288
builder.start(self.resource_type, dict())
240289
self.serialize(builder)
241290
builder.end(self.resource_type)
291+
292+
def message(self):
293+
builder = TreeBuilder()
294+
self.serialize_all(builder)
242295
msg = tostring(builder.close())
243296
return msg
244297

245298

299+
class ResourceInfo(StaticResourceInfo):
300+
301+
def __init__(self):
302+
self.write_all = False
303+
self.dom = None
304+
self.dirty = dict()
305+
306+
def _clear_subclasses(self):
307+
sbcs = [k for k, v in vars(self).items() if issubclass(type(v), StaticResourceInfo)]
308+
for sbc in sbcs:
309+
delattr(self, sbc)
310+
311+
def fetch(self):
312+
self.dom = self.catalog.get_xml(self.href)
313+
314+
def clear(self):
315+
self.dirty = dict()
316+
self._clear_subclasses()
317+
318+
def refresh(self):
319+
self.clear()
320+
self.fetch()
321+
322+
246323
def prepare_upload_bundle(name, data):
247324
"""GeoServer's REST API uses ZIP archives as containers for file formats such
248325
as Shapefile and WorldImage which include several 'boxcar' files alongside

0 commit comments

Comments
 (0)