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.infra.datanode;
19  
20  import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
21  import com.google.common.base.Objects;
22  import com.google.common.base.Splitter;
23  import lombok.Getter;
24  import lombok.RequiredArgsConstructor;
25  import lombok.ToString;
26  import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
27  import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
28  import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
29  import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
30  import org.apache.shardingsphere.infra.exception.kernel.metadata.datanode.InvalidDataNodeFormatException;
31  
32  import java.util.List;
33  
34  /**
35   * Data node.
36   */
37  @RequiredArgsConstructor
38  @Getter
39  @ToString
40  public final class DataNode {
41      
42      private static final String DELIMITER = ".";
43      
44      private static final String ASTERISK = "*";
45      
46      private final String dataSourceName;
47      
48      private final String schemaName;
49      
50      private final String tableName;
51      
52      /**
53       * Constructs a data node with well-formatted string.
54       *
55       * @param dataNode string of data node. use {@code .} to split data source name and table name.
56       */
57      public DataNode(final String dataNode) {
58          validateDataNodeFormat(dataNode);
59          List<String> segments = Splitter.on(DELIMITER).splitToList(dataNode);
60          boolean isIncludeSchema = 3 == segments.size();
61          dataSourceName = segments.get(0);
62          schemaName = isIncludeSchema ? segments.get(1) : null;
63          tableName = segments.get(isIncludeSchema ? 2 : 1);
64      }
65      
66      /**
67       * Constructs a data node with well-formatted string.
68       *
69       * @param databaseName database name
70       * @param databaseType database type
71       * @param dataNode data node use {@code .} to split schema name and table name
72       */
73      public DataNode(final String databaseName, final DatabaseType databaseType, final String dataNode) {
74          ShardingSpherePreconditions.checkState(dataNode.contains(DELIMITER), () -> new InvalidDataNodeFormatException(dataNode));
75          DialectDatabaseMetaData dialectDatabaseMetaData = new DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData();
76          boolean containsSchema = dialectDatabaseMetaData.getSchemaOption().isSchemaAvailable() && isValidDataNode(dataNode, 3);
77          List<String> segments = Splitter.on(DELIMITER).limit(containsSchema ? 3 : 2).splitToList(dataNode);
78          dataSourceName = segments.get(0);
79          schemaName = getSchemaName(databaseName, dialectDatabaseMetaData, containsSchema, segments);
80          tableName = containsSchema ? segments.get(2).toLowerCase() : segments.get(1).toLowerCase();
81      }
82      
83      private String getSchemaName(final String databaseName, final DialectDatabaseMetaData dialectDatabaseMetaData, final boolean containsSchema, final List<String> segments) {
84          return dialectDatabaseMetaData.getSchemaOption().getDefaultSchema().map(optional -> containsSchema ? segments.get(1) : ASTERISK).orElse(databaseName);
85      }
86      
87      private boolean isValidDataNode(final String dataNodeStr, final int tier) {
88          if (hasInvalidDelimiterStructure(dataNodeStr)) {
89              return false;
90          }
91          List<String> segments = Splitter.on(DELIMITER).splitToList(dataNodeStr);
92          return isAnySegmentIsEmptyOrContainsOnlyWhitespace(tier, segments);
93      }
94      
95      private boolean hasInvalidDelimiterStructure(final String dataNodeStr) {
96          return !dataNodeStr.contains(DELIMITER) || hasLeadingOrTrailingDelimiter(dataNodeStr) || hasConsecutiveDelimiters(dataNodeStr) || hasWhitespaceAroundDelimiters(dataNodeStr);
97      }
98      
99      private boolean hasLeadingOrTrailingDelimiter(final String dataNodeStr) {
100         return dataNodeStr.startsWith(DELIMITER) || dataNodeStr.endsWith(DELIMITER);
101     }
102     
103     private boolean hasConsecutiveDelimiters(final String dataNodeStr) {
104         return dataNodeStr.contains(DELIMITER + DELIMITER);
105     }
106     
107     private boolean hasWhitespaceAroundDelimiters(final String dataNodeStr) {
108         return dataNodeStr.contains(" " + DELIMITER) || dataNodeStr.contains(DELIMITER + " ");
109     }
110     
111     private boolean isAnySegmentIsEmptyOrContainsOnlyWhitespace(final int tier, final List<String> segments) {
112         return segments.stream().noneMatch(each -> each.trim().isEmpty()) && tier == segments.size();
113     }
114     
115     /**
116      * Validates the data node format based on its structure.
117      *
118      * @param dataNode the data node string to validate
119      * @throws InvalidDataNodeFormatException if the format is invalid
120      */
121     private void validateDataNodeFormat(final String dataNode) {
122         ShardingSpherePreconditions.checkState(isValidDataNode(dataNode, 2) || isValidDataNode(dataNode, 3), () -> new InvalidDataNodeFormatException(dataNode));
123     }
124     
125     /**
126      * Format data node as string with schema.
127      *
128      * @return formatted data node
129      */
130     public String format() {
131         return null == schemaName ? formatWithoutSchema() : formatWithSchema();
132     }
133     
134     /**
135      * Format data node as string.
136      *
137      * @param databaseType database type
138      * @return formatted data node
139      */
140     public String format(final DatabaseType databaseType) {
141         return shouldIncludeSchema(databaseType) ? formatWithSchema() : formatWithoutSchema();
142     }
143     
144     private boolean shouldIncludeSchema(final DatabaseType databaseType) {
145         return null != schemaName && new DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData().getSchemaOption().getDefaultSchema().isPresent();
146     }
147     
148     private String formatWithSchema() {
149         return String.join(DELIMITER, dataSourceName, schemaName, tableName);
150     }
151     
152     private String formatWithoutSchema() {
153         return String.join(DELIMITER, dataSourceName, tableName);
154     }
155     
156     @Override
157     public boolean equals(final Object object) {
158         if (this == object) {
159             return true;
160         }
161         if (null == object || getClass() != object.getClass()) {
162             return false;
163         }
164         DataNode dataNode = (DataNode) object;
165         return Objects.equal(new CaseInsensitiveString(dataSourceName), new CaseInsensitiveString(dataNode.dataSourceName))
166                 && Objects.equal(new CaseInsensitiveString(tableName), new CaseInsensitiveString(dataNode.tableName))
167                 && Objects.equal(null == schemaName ? null : new CaseInsensitiveString(schemaName), null == dataNode.schemaName ? null : new CaseInsensitiveString(dataNode.schemaName));
168     }
169     
170     @Override
171     public int hashCode() {
172         return Objects.hashCode(new CaseInsensitiveString(dataSourceName), new CaseInsensitiveString(tableName), null == schemaName ? null : new CaseInsensitiveString(schemaName));
173     }
174 }