View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.encrypt.rule;
19  
20  import com.cedarsoftware.util.CaseInsensitiveMap;
21  import com.cedarsoftware.util.CaseInsensitiveSet;
22  import org.apache.shardingsphere.encrypt.config.EncryptRuleConfiguration;
23  import org.apache.shardingsphere.encrypt.config.rule.EncryptColumnRuleConfiguration;
24  import org.apache.shardingsphere.encrypt.config.rule.EncryptTableRuleConfiguration;
25  import org.apache.shardingsphere.encrypt.constant.EncryptOrder;
26  import org.apache.shardingsphere.encrypt.exception.metadata.EncryptTableNotFoundException;
27  import org.apache.shardingsphere.encrypt.exception.metadata.MismatchedEncryptAlgorithmTypeException;
28  import org.apache.shardingsphere.encrypt.rule.attribute.EncryptTableMapperRuleAttribute;
29  import org.apache.shardingsphere.encrypt.rule.table.EncryptTable;
30  import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
31  import org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
32  import org.apache.shardingsphere.infra.annotation.HighFrequencyInvocation;
33  import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
34  import org.apache.shardingsphere.infra.rule.PartialRuleUpdateSupported;
35  import org.apache.shardingsphere.infra.rule.attribute.RuleAttribute;
36  import org.apache.shardingsphere.infra.rule.attribute.RuleAttributes;
37  import org.apache.shardingsphere.infra.rule.scope.DatabaseRule;
38  import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
39  
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.LinkedList;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.Map.Entry;
46  import java.util.Optional;
47  import java.util.concurrent.ConcurrentHashMap;
48  import java.util.concurrent.atomic.AtomicReference;
49  import java.util.stream.Collectors;
50  
51  /**
52   * Encrypt rule.
53   */
54  public final class EncryptRule implements DatabaseRule, PartialRuleUpdateSupported<EncryptRuleConfiguration> {
55      
56      private final String databaseName;
57      
58      private final AtomicReference<EncryptRuleConfiguration> ruleConfig = new AtomicReference<>();
59      
60      private final Map<String, EncryptAlgorithm> encryptors;
61      
62      private final Map<String, EncryptTable> tables = new CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentHashMap<>());
63      
64      private final AtomicReference<RuleAttributes> attributes = new AtomicReference<>();
65      
66      public EncryptRule(final String databaseName, final EncryptRuleConfiguration ruleConfig) {
67          this.databaseName = databaseName;
68          this.ruleConfig.set(ruleConfig);
69          encryptors = createEncryptors(ruleConfig);
70          for (EncryptTableRuleConfiguration each : ruleConfig.getTables()) {
71              each.getColumns().forEach(this::checkEncryptorType);
72              tables.put(each.getName(), new EncryptTable(each, encryptors));
73          }
74          attributes.set(buildRuleAttributes());
75      }
76      
77      private RuleAttributes buildRuleAttributes() {
78          List<RuleAttribute> ruleAttributes = new LinkedList<>();
79          ruleAttributes.add(new EncryptTableMapperRuleAttribute(tables.keySet()));
80          return new RuleAttributes(ruleAttributes.toArray(new RuleAttribute[]{}));
81      }
82      
83      private Map<String, EncryptAlgorithm> createEncryptors(final EncryptRuleConfiguration ruleConfig) {
84          Map<String, EncryptAlgorithm> result = new CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentHashMap<>(ruleConfig.getEncryptors().size(), 1F));
85          for (Entry<String, AlgorithmConfiguration> entry : ruleConfig.getEncryptors().entrySet()) {
86              result.put(entry.getKey(), TypedSPILoader.getService(EncryptAlgorithm.class, entry.getValue().getType(), entry.getValue().getProps()));
87          }
88          return result;
89      }
90      
91      // TODO How to process changed encryptors and tables if check failed? It should check before rule change
92      private void checkEncryptorType(final EncryptColumnRuleConfiguration columnRuleConfig) {
93          ShardingSpherePreconditions.checkState(encryptors.containsKey(columnRuleConfig.getCipher().getEncryptorName())
94                  && encryptors.get(columnRuleConfig.getCipher().getEncryptorName()).getMetaData().isSupportDecrypt(),
95                  () -> new MismatchedEncryptAlgorithmTypeException(databaseName, "Cipher", columnRuleConfig.getCipher().getEncryptorName(), "decrypt"));
96          columnRuleConfig.getAssistedQuery().ifPresent(optional -> ShardingSpherePreconditions.checkState(encryptors.containsKey(optional.getEncryptorName())
97                  && encryptors.get(optional.getEncryptorName()).getMetaData().isSupportEquivalentFilter(),
98                  () -> new MismatchedEncryptAlgorithmTypeException(databaseName, "Assisted query", columnRuleConfig.getCipher().getEncryptorName(), "equivalent filter")));
99          columnRuleConfig.getLikeQuery().ifPresent(optional -> ShardingSpherePreconditions.checkState(encryptors.containsKey(optional.getEncryptorName())
100                 && encryptors.get(optional.getEncryptorName()).getMetaData().isSupportLike(),
101                 () -> new MismatchedEncryptAlgorithmTypeException(databaseName, "Like query", columnRuleConfig.getCipher().getEncryptorName(), "like")));
102     }
103     
104     /**
105      * Get all table names.
106      *
107      * @return all table names
108      */
109     public Collection<String> getAllTableNames() {
110         return tables.keySet();
111     }
112     
113     /**
114      * Find encrypt table.
115      *
116      * @param tableName table name
117      * @return encrypt table
118      */
119     @HighFrequencyInvocation
120     public Optional<EncryptTable> findEncryptTable(final String tableName) {
121         return Optional.ofNullable(tables.get(tableName));
122     }
123     
124     /**
125      * Get encrypt table.
126      *
127      * @param tableName table name
128      * @return encrypt table
129      */
130     @HighFrequencyInvocation
131     public EncryptTable getEncryptTable(final String tableName) {
132         return findEncryptTable(tableName).orElseThrow(() -> new EncryptTableNotFoundException(tableName));
133     }
134     
135     /**
136      * Find query encryptor.
137      *
138      * @param tableName table name
139      * @param columnName column name
140      * @return query encryptor
141      */
142     @HighFrequencyInvocation
143     public Optional<EncryptAlgorithm> findQueryEncryptor(final String tableName, final String columnName) {
144         return findEncryptTable(tableName).flatMap(optional -> optional.findQueryEncryptor(columnName));
145     }
146     
147     @Override
148     public RuleAttributes getAttributes() {
149         return attributes.get();
150     }
151     
152     @Override
153     public EncryptRuleConfiguration getConfiguration() {
154         return ruleConfig.get();
155     }
156     
157     @Override
158     public void updateConfiguration(final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
159         ruleConfig.set(toBeUpdatedRuleConfig);
160     }
161     
162     @Override
163     public boolean partialUpdate(final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
164         if (handleAddedEncryptors(toBeUpdatedRuleConfig) || handleRemovedEncryptors(toBeUpdatedRuleConfig)) {
165             return false;
166         }
167         Collection<String> toBeUpdatedTablesNames = toBeUpdatedRuleConfig.getTables().stream().map(EncryptTableRuleConfiguration::getName).collect(Collectors.toCollection(CaseInsensitiveSet::new));
168         Collection<String> toBeRemovedTableNames = tables.keySet().stream().filter(each -> !toBeUpdatedTablesNames.contains(each)).collect(Collectors.toList());
169         if (!toBeRemovedTableNames.isEmpty()) {
170             toBeRemovedTableNames.forEach(tables::remove);
171         }
172         for (EncryptTableRuleConfiguration encryptTableRuleConfiguration : toBeUpdatedRuleConfig.getTables()) {
173             encryptTableRuleConfiguration.getColumns().forEach(this::checkEncryptorType);
174             tables.put(encryptTableRuleConfiguration.getName(), new EncryptTable(encryptTableRuleConfiguration, encryptors));
175             attributes.set(buildRuleAttributes());
176         }
177         return true;
178     }
179     
180     private boolean handleAddedEncryptors(final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
181         return toBeUpdatedRuleConfig.getEncryptors().entrySet().stream()
182                 .filter(entry -> !encryptors.containsKey(entry.getKey()))
183                 .peek(entry -> encryptors.computeIfAbsent(entry.getKey(), key -> TypedSPILoader.getService(EncryptAlgorithm.class, entry.getValue().getType(), entry.getValue().getProps())))
184                 .findAny().isPresent();
185     }
186     
187     private boolean handleRemovedEncryptors(final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
188         return encryptors.entrySet().stream()
189                 .filter(entry -> !toBeUpdatedRuleConfig.getEncryptors().containsKey(entry.getKey()))
190                 .peek(entry -> encryptors.remove(entry.getKey())).findAny().isPresent();
191     }
192     
193     @Override
194     public int getOrder() {
195         return EncryptOrder.ORDER;
196     }
197 }