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.google.common.base.Preconditions;
21  import org.apache.shardingsphere.encrypt.api.config.EncryptRuleConfiguration;
22  import org.apache.shardingsphere.encrypt.api.config.rule.EncryptColumnRuleConfiguration;
23  import org.apache.shardingsphere.encrypt.api.config.rule.EncryptTableRuleConfiguration;
24  import org.apache.shardingsphere.encrypt.exception.metadata.EncryptTableNotFoundException;
25  import org.apache.shardingsphere.encrypt.exception.metadata.MismatchedEncryptAlgorithmTypeException;
26  import org.apache.shardingsphere.encrypt.rule.attribute.EncryptTableMapperRuleAttribute;
27  import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
28  import org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
29  import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
30  import org.apache.shardingsphere.infra.rule.PartialRuleUpdateSupported;
31  import org.apache.shardingsphere.infra.rule.attribute.RuleAttributes;
32  import org.apache.shardingsphere.infra.rule.scope.DatabaseRule;
33  import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
34  
35  import java.util.Collection;
36  import java.util.HashSet;
37  import java.util.Map.Entry;
38  import java.util.Optional;
39  import java.util.concurrent.ConcurrentHashMap;
40  import java.util.concurrent.atomic.AtomicReference;
41  import java.util.stream.Collectors;
42  
43  /**
44   * Encrypt rule.
45   */
46  public final class EncryptRule implements DatabaseRule, PartialRuleUpdateSupported<EncryptRuleConfiguration> {
47      
48      private final String databaseName;
49      
50      private final AtomicReference<EncryptRuleConfiguration> ruleConfig = new AtomicReference<>();
51      
52      private final ConcurrentHashMap<String, EncryptAlgorithm> encryptors;
53      
54      private final ConcurrentHashMap<String, EncryptTable> tables = new ConcurrentHashMap<>();
55      
56      private final AtomicReference<RuleAttributes> attributes = new AtomicReference<>();
57      
58      public EncryptRule(final String databaseName, final EncryptRuleConfiguration ruleConfig) {
59          this.databaseName = databaseName;
60          this.ruleConfig.set(ruleConfig);
61          encryptors = createEncryptors(ruleConfig);
62          for (EncryptTableRuleConfiguration each : ruleConfig.getTables()) {
63              each.getColumns().forEach(this::checkEncryptorType);
64              tables.put(each.getName().toLowerCase(), new EncryptTable(each, encryptors));
65          }
66          attributes.set(new RuleAttributes(new EncryptTableMapperRuleAttribute(tables.keySet())));
67      }
68      
69      private ConcurrentHashMap<String, EncryptAlgorithm> createEncryptors(final EncryptRuleConfiguration ruleConfig) {
70          ConcurrentHashMap<String, EncryptAlgorithm> result = new ConcurrentHashMap<>(ruleConfig.getEncryptors().size(), 1F);
71          for (Entry<String, AlgorithmConfiguration> entry : ruleConfig.getEncryptors().entrySet()) {
72              result.put(entry.getKey(), TypedSPILoader.getService(EncryptAlgorithm.class, entry.getValue().getType(), entry.getValue().getProps()));
73          }
74          return result;
75      }
76      
77      // TODO How to process changed encryptors and tables if check failed? It should check before rule change
78      private void checkEncryptorType(final EncryptColumnRuleConfiguration columnRuleConfig) {
79          ShardingSpherePreconditions.checkState(encryptors.containsKey(columnRuleConfig.getCipher().getEncryptorName())
80                  && encryptors.get(columnRuleConfig.getCipher().getEncryptorName()).getMetaData().isSupportDecrypt(),
81                  () -> new MismatchedEncryptAlgorithmTypeException(databaseName, "Cipher", columnRuleConfig.getCipher().getEncryptorName(), "decrypt"));
82          columnRuleConfig.getAssistedQuery().ifPresent(optional -> ShardingSpherePreconditions.checkState(encryptors.containsKey(optional.getEncryptorName())
83                  && encryptors.get(optional.getEncryptorName()).getMetaData().isSupportEquivalentFilter(),
84                  () -> new MismatchedEncryptAlgorithmTypeException(databaseName, "Assisted query", columnRuleConfig.getCipher().getEncryptorName(), "equivalent filter")));
85          columnRuleConfig.getLikeQuery().ifPresent(optional -> ShardingSpherePreconditions.checkState(encryptors.containsKey(optional.getEncryptorName())
86                  && encryptors.get(optional.getEncryptorName()).getMetaData().isSupportLike(),
87                  () -> new MismatchedEncryptAlgorithmTypeException(databaseName, "Like query", columnRuleConfig.getCipher().getEncryptorName(), "like")));
88      }
89      
90      /**
91       * Get all table names.
92       *
93       * @return all table names
94       */
95      public Collection<String> getAllTableNames() {
96          return tables.keySet();
97      }
98      
99      /**
100      * Find encrypt table.
101      * 
102      * @param tableName table name
103      * @return encrypt table
104      */
105     public Optional<EncryptTable> findEncryptTable(final String tableName) {
106         return Optional.ofNullable(tables.get(tableName.toLowerCase()));
107     }
108     
109     /**
110      * Get encrypt table.
111      *
112      * @param tableName table name
113      * @return encrypt table
114      */
115     public EncryptTable getEncryptTable(final String tableName) {
116         Optional<EncryptTable> encryptTable = findEncryptTable(tableName);
117         ShardingSpherePreconditions.checkState(encryptTable.isPresent(), () -> new EncryptTableNotFoundException(tableName));
118         return encryptTable.get();
119     }
120     
121     @Override
122     public RuleAttributes getAttributes() {
123         return attributes.get();
124     }
125     
126     @Override
127     public EncryptRuleConfiguration getConfiguration() {
128         return ruleConfig.get();
129     }
130     
131     @Override
132     public void updateConfiguration(final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
133         ruleConfig.set(toBeUpdatedRuleConfig);
134     }
135     
136     @Override
137     public boolean partialUpdate(final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
138         Collection<String> toBeAddedTableNames = toBeUpdatedRuleConfig.getTables().stream().map(EncryptTableRuleConfiguration::getName).filter(each -> !tables.containsKey(each.toLowerCase()))
139                 .collect(Collectors.toList());
140         if (!toBeAddedTableNames.isEmpty()) {
141             toBeAddedTableNames.forEach(each -> addTableRule(each, toBeUpdatedRuleConfig));
142             attributes.set(new RuleAttributes(new EncryptTableMapperRuleAttribute(tables.keySet())));
143             return true;
144         }
145         Collection<String> toBeRemovedTableNames = new HashSet<>(tables.keySet());
146         toBeRemovedTableNames.removeAll(toBeUpdatedRuleConfig.getTables().stream().map(EncryptTableRuleConfiguration::getName).collect(Collectors.toList()));
147         if (!toBeRemovedTableNames.isEmpty()) {
148             toBeRemovedTableNames.stream().map(String::toLowerCase).forEach(tables::remove);
149             attributes.set(new RuleAttributes(new EncryptTableMapperRuleAttribute(tables.keySet())));
150             // TODO check and remove unused INLINE encryptors
151             return true;
152         }
153         // TODO Process update table
154         // TODO Process CRUD encryptors
155         return false;
156     }
157     
158     private void addTableRule(final String tableName, final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
159         EncryptTableRuleConfiguration tableRuleConfig = getTableRuleConfiguration(tableName, toBeUpdatedRuleConfig);
160         for (Entry<String, AlgorithmConfiguration> entry : toBeUpdatedRuleConfig.getEncryptors().entrySet()) {
161             encryptors.computeIfAbsent(entry.getKey(), key -> TypedSPILoader.getService(EncryptAlgorithm.class, entry.getValue().getType(), entry.getValue().getProps()));
162         }
163         tableRuleConfig.getColumns().forEach(this::checkEncryptorType);
164         tables.put(tableName.toLowerCase(), new EncryptTable(tableRuleConfig, encryptors));
165     }
166     
167     private EncryptTableRuleConfiguration getTableRuleConfiguration(final String tableName, final EncryptRuleConfiguration toBeUpdatedRuleConfig) {
168         Optional<EncryptTableRuleConfiguration> result = toBeUpdatedRuleConfig.getTables().stream().filter(table -> table.getName().equals(tableName)).findFirst();
169         Preconditions.checkState(result.isPresent());
170         return result.get();
171     }
172 }