diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index b3b2b1eb83..ba300eec71 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -43,7 +43,6 @@ public class OAuth2Controller extends BaseController { @Autowired private OAuth2Service oauth2Service; - // TODO ask why POST @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java new file mode 100644 index 0000000000..5de01b3e2c --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.lock; + +public enum LockKey { + OAUTH2_CONFIG(999); + + private int id; + + LockKey(int id) { + this.id = id; + } + + public int getId() { + return id; + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java new file mode 100644 index 0000000000..4ffd05233d --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.lock; + +public interface LockService { + /** + * Lock with SQL Database till the end of transaction + * + * @param key identifier + */ + void transactionLock(LockKey key); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java new file mode 100644 index 0000000000..703a6686a8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.lock; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.sql.lock.LockRepository; + +import javax.annotation.PostConstruct; + +@Service +@Slf4j +public class LockServiceImpl implements LockService { + + @Autowired(required = false) + private LockRepository lockRepository; + + @PostConstruct + public void init(){ + log.warn("Locking with DB is not enabled."); + } + + @Override + public void transactionLock(LockKey key) { + if (lockRepository == null) return; + log.trace("Locking transaction key [{}]", key); + lockRepository.transactionLock(key.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 92790b3d98..90d19a19f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,6 +28,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.*; @@ -36,13 +37,14 @@ import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.lock.LockKey; +import org.thingsboard.server.dao.lock.LockService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantService; import java.io.IOException; import java.util.*; import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -51,11 +53,8 @@ import static org.thingsboard.server.dao.oauth2.OAuth2Utils.*; @Slf4j @Service public class OAuth2ServiceImpl implements OAuth2Service { - private static final ObjectMapper mapper = new ObjectMapper(); - private final ReentrantLock clientRegistrationSaveLock = new ReentrantLock(); - @Autowired private Environment environment; @@ -68,6 +67,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired private TenantService tenantService; + @Autowired + private LockService lockService; + private boolean isInstall() { return environment.acceptsProfiles("install"); } @@ -123,52 +125,59 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { validate(oAuth2ClientsParams); - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - clientRegistrationSaveLock.lock(); - try { - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - if (oauth2SystemAdminSettings == null) { - oauth2SystemAdminSettings = createSystemAdminSettings(); - } - String json = toJson(oAuth2ClientsParams); - ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); - } finally { - clientRegistrationSaveLock.unlock(); - } + + transactionalSaveSystemOAuth2ClientsParams(oAuth2ClientsParams); return getSystemOAuth2ClientsParams(); } + @Transactional + private void transactionalSaveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { + long acquireStart = System.currentTimeMillis(); + lockService.transactionLock(LockKey.OAUTH2_CONFIG); + log.trace("[{}] Waited for lock {} ms.", TenantId.SYS_TENANT_ID, System.currentTimeMillis() - acquireStart); + + validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + if (oauth2SystemAdminSettings == null) { + oauth2SystemAdminSettings = createSystemAdminSettings(); + } + String json = toJson(oAuth2ClientsParams); + ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); + } + @Override public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { validate(oAuth2ClientsParams); - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - clientRegistrationSaveLock.lock(); - try { - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - Set domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() - .map(OAuth2ClientsDomainParams::getDomainName) - .collect(Collectors.toSet()); - processTenantAdminSettings(tenantId, domainNames); + transactionalSaveTenantOAuth2ClientsParams(tenantId, oAuth2ClientsParams); - List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); - try { - attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); - } catch (Exception e) { - log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); - throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); - } + return getTenantOAuth2ClientsParams(tenantId); + } - } finally { - clientRegistrationSaveLock.unlock(); - } + @Transactional + private void transactionalSaveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { + long acquireStart = System.currentTimeMillis(); + lockService.transactionLock(LockKey.OAUTH2_CONFIG); + log.trace("[{}] Waited for lock {} ms.", tenantId, System.currentTimeMillis() - acquireStart); - return getTenantOAuth2ClientsParams(tenantId); + validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); + + Set domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() + .map(OAuth2ClientsDomainParams::getDomainName) + .collect(Collectors.toSet()); + processTenantAdminSettings(tenantId, domainNames); + + List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); + try { + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); + } catch (Exception e) { + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); + } } private List createOAuth2ClientsParamsAttributes(OAuth2ClientsParams oAuth2ClientsParams) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java new file mode 100644 index 0000000000..eced945acd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.lock; + +public interface LockRepository { + void transactionLock(Integer key); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java new file mode 100644 index 0000000000..0ae44edffa --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.lock; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@Slf4j +@SqlDao +@PsqlDao +@Repository +public class PsqlLockRepository implements LockRepository { + private static final String TRANSACTION_LOCK_QUERY = "SELECT pg_advisory_xact_lock(?)"; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public void transactionLock(Integer key) { + jdbcTemplate.query(TRANSACTION_LOCK_QUERY, + preparedStatement -> preparedStatement.setInt(1, key), + resultSet -> {} + ); + } +}