Commit 9eb9302ee3070e5af69f9d410d39bc2a312c7609

Authored by vzikratyi
1 parent d8fecd18

Used DB lock instead of Java lock

... ... @@ -43,7 +43,6 @@ public class OAuth2Controller extends BaseController {
43 43 @Autowired
44 44 private OAuth2Service oauth2Service;
45 45
46   - // TODO ask why POST
47 46 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
48 47 @ResponseBody
49 48 public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.lock;
  17 +
  18 +public enum LockKey {
  19 + OAUTH2_CONFIG(999);
  20 +
  21 + private int id;
  22 +
  23 + LockKey(int id) {
  24 + this.id = id;
  25 + }
  26 +
  27 + public int getId() {
  28 + return id;
  29 + }
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.lock;
  17 +
  18 +public interface LockService {
  19 + /**
  20 + * Lock with SQL Database till the end of transaction
  21 + *
  22 + * @param key identifier
  23 + */
  24 + void transactionLock(LockKey key);
  25 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.lock;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.stereotype.Service;
  21 +import org.thingsboard.server.dao.sql.lock.LockRepository;
  22 +
  23 +import javax.annotation.PostConstruct;
  24 +
  25 +@Service
  26 +@Slf4j
  27 +public class LockServiceImpl implements LockService {
  28 +
  29 + @Autowired(required = false)
  30 + private LockRepository lockRepository;
  31 +
  32 + @PostConstruct
  33 + public void init(){
  34 + log.warn("Locking with DB is not enabled.");
  35 + }
  36 +
  37 + @Override
  38 + public void transactionLock(LockKey key) {
  39 + if (lockRepository == null) return;
  40 + log.trace("Locking transaction key [{}]", key);
  41 + lockRepository.transactionLock(key.getId());
  42 + }
  43 +}
... ...
1 1 /**
2 2 * Copyright © 2016-2020 The Thingsboard Authors
3   - * <p>
  3 + *
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7   - * <p>
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - * <p>
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
... ... @@ -28,6 +28,7 @@ import org.apache.commons.lang3.tuple.Pair;
28 28 import org.springframework.beans.factory.annotation.Autowired;
29 29 import org.springframework.core.env.Environment;
30 30 import org.springframework.stereotype.Service;
  31 +import org.springframework.transaction.annotation.Transactional;
31 32 import org.springframework.util.StringUtils;
32 33 import org.thingsboard.server.common.data.*;
33 34 import org.thingsboard.server.common.data.id.*;
... ... @@ -36,13 +37,14 @@ import org.thingsboard.server.common.data.oauth2.*;
36 37 import org.thingsboard.server.dao.attributes.AttributesService;
37 38 import org.thingsboard.server.dao.exception.DataValidationException;
38 39 import org.thingsboard.server.dao.exception.IncorrectParameterException;
  40 +import org.thingsboard.server.dao.lock.LockKey;
  41 +import org.thingsboard.server.dao.lock.LockService;
39 42 import org.thingsboard.server.dao.settings.AdminSettingsService;
40 43 import org.thingsboard.server.dao.tenant.TenantService;
41 44
42 45 import java.io.IOException;
43 46 import java.util.*;
44 47 import java.util.concurrent.ExecutionException;
45   -import java.util.concurrent.locks.ReentrantLock;
46 48 import java.util.function.Consumer;
47 49 import java.util.stream.Collectors;
48 50
... ... @@ -51,11 +53,8 @@ import static org.thingsboard.server.dao.oauth2.OAuth2Utils.*;
51 53 @Slf4j
52 54 @Service
53 55 public class OAuth2ServiceImpl implements OAuth2Service {
54   -
55 56 private static final ObjectMapper mapper = new ObjectMapper();
56 57
57   - private final ReentrantLock clientRegistrationSaveLock = new ReentrantLock();
58   -
59 58 @Autowired
60 59 private Environment environment;
61 60
... ... @@ -68,6 +67,9 @@ public class OAuth2ServiceImpl implements OAuth2Service {
68 67 @Autowired
69 68 private TenantService tenantService;
70 69
  70 + @Autowired
  71 + private LockService lockService;
  72 +
71 73 private boolean isInstall() {
72 74 return environment.acceptsProfiles("install");
73 75 }
... ... @@ -123,52 +125,59 @@ public class OAuth2ServiceImpl implements OAuth2Service {
123 125 @Override
124 126 public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) {
125 127 validate(oAuth2ClientsParams);
126   -
127 128 validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID);
128   - clientRegistrationSaveLock.lock();
129   - try {
130   - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID);
131   - AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS);
132   - if (oauth2SystemAdminSettings == null) {
133   - oauth2SystemAdminSettings = createSystemAdminSettings();
134   - }
135   - String json = toJson(oAuth2ClientsParams);
136   - ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json);
137   - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings);
138   - } finally {
139   - clientRegistrationSaveLock.unlock();
140   - }
  129 +
  130 + transactionalSaveSystemOAuth2ClientsParams(oAuth2ClientsParams);
141 131
142 132 return getSystemOAuth2ClientsParams();
143 133 }
144 134
  135 + @Transactional
  136 + private void transactionalSaveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) {
  137 + long acquireStart = System.currentTimeMillis();
  138 + lockService.transactionLock(LockKey.OAUTH2_CONFIG);
  139 + log.trace("[{}] Waited for lock {} ms.", TenantId.SYS_TENANT_ID, System.currentTimeMillis() - acquireStart);
  140 +
  141 + validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID);
  142 + AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS);
  143 + if (oauth2SystemAdminSettings == null) {
  144 + oauth2SystemAdminSettings = createSystemAdminSettings();
  145 + }
  146 + String json = toJson(oAuth2ClientsParams);
  147 + ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json);
  148 + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings);
  149 + }
  150 +
145 151 @Override
146 152 public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) {
147 153 validate(oAuth2ClientsParams);
148   -
149 154 validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId);
150   - clientRegistrationSaveLock.lock();
151   - try {
152   - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId);
153 155
154   - Set<String> domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream()
155   - .map(OAuth2ClientsDomainParams::getDomainName)
156   - .collect(Collectors.toSet());
157   - processTenantAdminSettings(tenantId, domainNames);
  156 + transactionalSaveTenantOAuth2ClientsParams(tenantId, oAuth2ClientsParams);
158 157
159   - List<AttributeKvEntry> attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams);
160   - try {
161   - attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get();
162   - } catch (Exception e) {
163   - log.error("Unable to save OAuth2 Client Registration Params to attributes!", e);
164   - throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!");
165   - }
  158 + return getTenantOAuth2ClientsParams(tenantId);
  159 + }
166 160
167   - } finally {
168   - clientRegistrationSaveLock.unlock();
169   - }
  161 + @Transactional
  162 + private void transactionalSaveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) {
  163 + long acquireStart = System.currentTimeMillis();
  164 + lockService.transactionLock(LockKey.OAUTH2_CONFIG);
  165 + log.trace("[{}] Waited for lock {} ms.", tenantId, System.currentTimeMillis() - acquireStart);
170 166
171   - return getTenantOAuth2ClientsParams(tenantId);
  167 + validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId);
  168 +
  169 + Set<String> domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream()
  170 + .map(OAuth2ClientsDomainParams::getDomainName)
  171 + .collect(Collectors.toSet());
  172 + processTenantAdminSettings(tenantId, domainNames);
  173 +
  174 + List<AttributeKvEntry> attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams);
  175 + try {
  176 + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get();
  177 + } catch (Exception e) {
  178 + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e);
  179 + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!");
  180 + }
172 181 }
173 182
174 183 private List<AttributeKvEntry> createOAuth2ClientsParamsAttributes(OAuth2ClientsParams oAuth2ClientsParams) {
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.lock;
  17 +
  18 +public interface LockRepository {
  19 + void transactionLock(Integer key);
  20 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.lock;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.jdbc.core.JdbcTemplate;
  21 +import org.springframework.stereotype.Repository;
  22 +import org.thingsboard.server.dao.util.PsqlDao;
  23 +import org.thingsboard.server.dao.util.SqlDao;
  24 +
  25 +@Slf4j
  26 +@SqlDao
  27 +@PsqlDao
  28 +@Repository
  29 +public class PsqlLockRepository implements LockRepository {
  30 + private static final String TRANSACTION_LOCK_QUERY = "SELECT pg_advisory_xact_lock(?)";
  31 +
  32 + @Autowired
  33 + private JdbcTemplate jdbcTemplate;
  34 +
  35 + @Override
  36 + public void transactionLock(Integer key) {
  37 + jdbcTemplate.query(TRANSACTION_LOCK_QUERY,
  38 + preparedStatement -> preparedStatement.setInt(1, key),
  39 + resultSet -> {}
  40 + );
  41 + }
  42 +}
... ...