525 changed files with 7734 additions and 8938 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,85 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.utils; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.concurrent.Executor; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* This class deduplicate executions of the specified function. |
|||
* Useful in cluster mode, when you get event about partition change multiple times. |
|||
* Assuming that the function execution is expensive, we should execute it immediately when first time event occurs and |
|||
* later, once the processing of first event is done, process last pending task. |
|||
* |
|||
* @param <P> parameters of the function |
|||
*/ |
|||
@Slf4j |
|||
public class EventDeduplicationExecutor<P> { |
|||
private final String name; |
|||
private final ExecutorService executor; |
|||
private final Consumer<P> function; |
|||
private P pendingTask; |
|||
private boolean busy; |
|||
|
|||
public EventDeduplicationExecutor(String name, ExecutorService executor, Consumer<P> function) { |
|||
this.name = name; |
|||
this.executor = executor; |
|||
this.function = function; |
|||
} |
|||
|
|||
public void submit(P params) { |
|||
log.info("[{}] Going to submit: {}", name, params); |
|||
synchronized (EventDeduplicationExecutor.this) { |
|||
if (!busy) { |
|||
busy = true; |
|||
pendingTask = null; |
|||
try { |
|||
log.info("[{}] Submitting task: {}", name, params); |
|||
executor.submit(() -> { |
|||
try { |
|||
log.info("[{}] Executing task: {}", name, params); |
|||
function.accept(params); |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to process task with parameters: {}", name, params, e); |
|||
throw e; |
|||
} finally { |
|||
unlockAndProcessIfAny(); |
|||
} |
|||
}); |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to submit task with parameters: {}", name, params, e); |
|||
unlockAndProcessIfAny(); |
|||
throw e; |
|||
} |
|||
} else { |
|||
log.info("[{}] Task is already in progress. {} pending task: {}", name, pendingTask == null ? "adding" : "updating", params); |
|||
pendingTask = params; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void unlockAndProcessIfAny() { |
|||
synchronized (EventDeduplicationExecutor.this) { |
|||
busy = false; |
|||
if (pendingTask != null) { |
|||
submit(pendingTask); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,58 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.service.mail; |
|||
|
|||
import org.mockito.Mockito; |
|||
import org.mockito.invocation.InvocationOnMock; |
|||
import org.mockito.stubbing.Answer; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.context.annotation.Primary; |
|||
import org.springframework.context.annotation.Profile; |
|||
import org.thingsboard.rule.engine.api.MailService; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
|
|||
@Profile("test") |
|||
@Configuration |
|||
public class TestMailService { |
|||
|
|||
public static String currentActivateToken; |
|||
public static String currentResetPasswordToken; |
|||
|
|||
@Bean |
|||
@Primary |
|||
public MailService mailService() throws ThingsboardException { |
|||
MailService mailService = Mockito.mock(MailService.class); |
|||
Mockito.doAnswer(new Answer<Void>() { |
|||
public Void answer(InvocationOnMock invocation) { |
|||
Object[] args = invocation.getArguments(); |
|||
String activationLink = (String) args[0]; |
|||
currentActivateToken = activationLink.split("=")[1]; |
|||
return null; |
|||
} |
|||
}).when(mailService).sendActivationEmail(Mockito.anyString(), Mockito.anyString()); |
|||
Mockito.doAnswer(new Answer<Void>() { |
|||
public Void answer(InvocationOnMock invocation) { |
|||
Object[] args = invocation.getArguments(); |
|||
String passwordResetLink = (String) args[0]; |
|||
currentResetPasswordToken = passwordResetLink.split("=")[1]; |
|||
return null; |
|||
} |
|||
}).when(mailService).sendResetPasswordEmailAsync(Mockito.anyString(), Mockito.anyString()); |
|||
return mailService; |
|||
} |
|||
|
|||
} |
|||
@ -1,173 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.util; |
|||
|
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.After; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.mockito.Mockito; |
|||
import org.mockito.junit.MockitoJUnitRunner; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.utils.EventDeduplicationExecutor; |
|||
|
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.function.Consumer; |
|||
|
|||
@Slf4j |
|||
@RunWith(MockitoJUnitRunner.class) |
|||
public class EventDeduplicationExecutorTest { |
|||
|
|||
ThingsBoardThreadFactory threadFactory = ThingsBoardThreadFactory.forName(getClass().getSimpleName()); |
|||
ExecutorService executor; |
|||
|
|||
@After |
|||
public void tearDown() throws Exception { |
|||
if (executor != null) { |
|||
executor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testSimpleFlowSameThread() throws InterruptedException { |
|||
simpleFlow(MoreExecutors.newDirectExecutorService()); |
|||
} |
|||
|
|||
@Test |
|||
public void testPeriodicFlowSameThread() throws InterruptedException { |
|||
periodicFlow(MoreExecutors.newDirectExecutorService()); |
|||
} |
|||
|
|||
@Test |
|||
public void testExceptionFlowSameThread() throws InterruptedException { |
|||
exceptionFlow(MoreExecutors.newDirectExecutorService()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSimpleFlowSingleThread() throws InterruptedException { |
|||
executor = Executors.newSingleThreadExecutor(threadFactory); |
|||
simpleFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testPeriodicFlowSingleThread() throws InterruptedException { |
|||
executor = Executors.newSingleThreadExecutor(threadFactory); |
|||
periodicFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testExceptionFlowSingleThread() throws InterruptedException { |
|||
executor = Executors.newSingleThreadExecutor(threadFactory); |
|||
exceptionFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testSimpleFlowMultiThread() throws InterruptedException { |
|||
executor = Executors.newFixedThreadPool(3, threadFactory); |
|||
simpleFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testPeriodicFlowMultiThread() throws InterruptedException { |
|||
executor = Executors.newFixedThreadPool(3, threadFactory); |
|||
periodicFlow(executor); |
|||
} |
|||
|
|||
@Test |
|||
public void testExceptionFlowMultiThread() throws InterruptedException { |
|||
executor = Executors.newFixedThreadPool(3, threadFactory); |
|||
exceptionFlow(executor); |
|||
} |
|||
|
|||
private void simpleFlow(ExecutorService executorService) throws InterruptedException { |
|||
try { |
|||
Consumer<String> function = Mockito.spy(StringConsumer.class); |
|||
EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); |
|||
|
|||
String params1 = "params1"; |
|||
String params2 = "params2"; |
|||
String params3 = "params3"; |
|||
|
|||
executor.submit(params1); |
|||
executor.submit(params2); |
|||
executor.submit(params3); |
|||
Thread.sleep(500); |
|||
Mockito.verify(function).accept(params1); |
|||
Mockito.verify(function).accept(params3); |
|||
} finally { |
|||
executorService.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
private void periodicFlow(ExecutorService executorService) throws InterruptedException { |
|||
try { |
|||
Consumer<String> function = Mockito.spy(StringConsumer.class); |
|||
EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); |
|||
|
|||
String params1 = "params1"; |
|||
String params2 = "params2"; |
|||
String params3 = "params3"; |
|||
|
|||
executor.submit(params1); |
|||
Thread.sleep(500); |
|||
executor.submit(params2); |
|||
Thread.sleep(500); |
|||
executor.submit(params3); |
|||
Thread.sleep(500); |
|||
Mockito.verify(function).accept(params1); |
|||
Mockito.verify(function).accept(params2); |
|||
Mockito.verify(function).accept(params3); |
|||
} finally { |
|||
executorService.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
private void exceptionFlow(ExecutorService executorService) throws InterruptedException { |
|||
try { |
|||
Consumer<String> function = Mockito.spy(StringConsumer.class); |
|||
EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); |
|||
|
|||
String params1 = "params1"; |
|||
String params2 = "params2"; |
|||
String params3 = "params3"; |
|||
|
|||
Mockito.doThrow(new RuntimeException()).when(function).accept("params1"); |
|||
executor.submit(params1); |
|||
executor.submit(params2); |
|||
Thread.sleep(500); |
|||
executor.submit(params3); |
|||
Thread.sleep(500); |
|||
Mockito.verify(function).accept(params2); |
|||
Mockito.verify(function).accept(params3); |
|||
} finally { |
|||
executorService.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
public static class StringConsumer implements Consumer<String> { |
|||
@Override |
|||
public void accept(String s) { |
|||
try { |
|||
Thread.sleep(100); |
|||
} catch (InterruptedException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,366 +0,0 @@ |
|||
/* |
|||
* Licensed to the Apache Software Foundation (ASF) under one |
|||
* or more contributor license agreements. See the NOTICE file |
|||
* distributed with this work for additional information |
|||
* regarding copyright ownership. The ASF licenses this file |
|||
* to you 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.apache.cassandra.io.sstable; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOError; |
|||
import java.io.IOException; |
|||
import java.util.*; |
|||
import java.util.regex.Pattern; |
|||
|
|||
import com.google.common.annotations.VisibleForTesting; |
|||
import com.google.common.base.CharMatcher; |
|||
import com.google.common.base.Objects; |
|||
|
|||
import org.apache.cassandra.db.Directories; |
|||
import org.apache.cassandra.io.sstable.format.SSTableFormat; |
|||
import org.apache.cassandra.io.sstable.format.Version; |
|||
import org.apache.cassandra.io.sstable.metadata.IMetadataSerializer; |
|||
import org.apache.cassandra.io.sstable.metadata.LegacyMetadataSerializer; |
|||
import org.apache.cassandra.io.sstable.metadata.MetadataSerializer; |
|||
import org.apache.cassandra.utils.Pair; |
|||
|
|||
import static org.apache.cassandra.io.sstable.Component.separator; |
|||
|
|||
/** |
|||
* A SSTable is described by the keyspace and column family it contains data |
|||
* for, a generation (where higher generations contain more recent data) and |
|||
* an alphabetic version string. |
|||
* |
|||
* A descriptor can be marked as temporary, which influences generated filenames. |
|||
*/ |
|||
public class Descriptor |
|||
{ |
|||
public static String TMP_EXT = ".tmp"; |
|||
|
|||
/** canonicalized path to the directory where SSTable resides */ |
|||
public final File directory; |
|||
/** version has the following format: <code>[a-z]+</code> */ |
|||
public final Version version; |
|||
public final String ksname; |
|||
public final String cfname; |
|||
public final int generation; |
|||
public final SSTableFormat.Type formatType; |
|||
/** digest component - might be {@code null} for old, legacy sstables */ |
|||
public final Component digestComponent; |
|||
private final int hashCode; |
|||
|
|||
/** |
|||
* A descriptor that assumes CURRENT_VERSION. |
|||
*/ |
|||
@VisibleForTesting |
|||
public Descriptor(File directory, String ksname, String cfname, int generation) |
|||
{ |
|||
this(SSTableFormat.Type.current().info.getLatestVersion(), directory, ksname, cfname, generation, SSTableFormat.Type.current(), null); |
|||
} |
|||
|
|||
/** |
|||
* Constructor for sstable writers only. |
|||
*/ |
|||
public Descriptor(File directory, String ksname, String cfname, int generation, SSTableFormat.Type formatType) |
|||
{ |
|||
this(formatType.info.getLatestVersion(), directory, ksname, cfname, generation, formatType, Component.digestFor(formatType.info.getLatestVersion().uncompressedChecksumType())); |
|||
} |
|||
|
|||
@VisibleForTesting |
|||
public Descriptor(String version, File directory, String ksname, String cfname, int generation, SSTableFormat.Type formatType) |
|||
{ |
|||
this(formatType.info.getVersion(version), directory, ksname, cfname, generation, formatType, Component.digestFor(formatType.info.getLatestVersion().uncompressedChecksumType())); |
|||
} |
|||
|
|||
public Descriptor(Version version, File directory, String ksname, String cfname, int generation, SSTableFormat.Type formatType, Component digestComponent) |
|||
{ |
|||
assert version != null && directory != null && ksname != null && cfname != null && formatType.info.getLatestVersion().getClass().equals(version.getClass()); |
|||
this.version = version; |
|||
try |
|||
{ |
|||
this.directory = directory.getCanonicalFile(); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new IOError(e); |
|||
} |
|||
this.ksname = ksname; |
|||
this.cfname = cfname; |
|||
this.generation = generation; |
|||
this.formatType = formatType; |
|||
this.digestComponent = digestComponent; |
|||
|
|||
hashCode = Objects.hashCode(version, this.directory, generation, ksname, cfname, formatType); |
|||
} |
|||
|
|||
public Descriptor withGeneration(int newGeneration) |
|||
{ |
|||
return new Descriptor(version, directory, ksname, cfname, newGeneration, formatType, digestComponent); |
|||
} |
|||
|
|||
public Descriptor withFormatType(SSTableFormat.Type newType) |
|||
{ |
|||
return new Descriptor(newType.info.getLatestVersion(), directory, ksname, cfname, generation, newType, digestComponent); |
|||
} |
|||
|
|||
public Descriptor withDigestComponent(Component newDigestComponent) |
|||
{ |
|||
return new Descriptor(version, directory, ksname, cfname, generation, formatType, newDigestComponent); |
|||
} |
|||
|
|||
public String tmpFilenameFor(Component component) |
|||
{ |
|||
return filenameFor(component) + TMP_EXT; |
|||
} |
|||
|
|||
public String filenameFor(Component component) |
|||
{ |
|||
return baseFilename() + separator + component.name(); |
|||
} |
|||
|
|||
public String baseFilename() |
|||
{ |
|||
StringBuilder buff = new StringBuilder(); |
|||
buff.append(directory).append(File.separatorChar); |
|||
appendFileName(buff); |
|||
return buff.toString(); |
|||
} |
|||
|
|||
private void appendFileName(StringBuilder buff) |
|||
{ |
|||
if (!version.hasNewFileName()) |
|||
{ |
|||
buff.append(ksname).append(separator); |
|||
buff.append(cfname).append(separator); |
|||
} |
|||
buff.append(version).append(separator); |
|||
buff.append(generation); |
|||
if (formatType != SSTableFormat.Type.LEGACY) |
|||
buff.append(separator).append(formatType.name); |
|||
} |
|||
|
|||
public String relativeFilenameFor(Component component) |
|||
{ |
|||
final StringBuilder buff = new StringBuilder(); |
|||
appendFileName(buff); |
|||
buff.append(separator).append(component.name()); |
|||
return buff.toString(); |
|||
} |
|||
|
|||
public SSTableFormat getFormat() |
|||
{ |
|||
return formatType.info; |
|||
} |
|||
|
|||
/** Return any temporary files found in the directory */ |
|||
public List<File> getTemporaryFiles() |
|||
{ |
|||
List<File> ret = new ArrayList<>(); |
|||
File[] tmpFiles = directory.listFiles((dir, name) -> |
|||
name.endsWith(Descriptor.TMP_EXT)); |
|||
|
|||
for (File tmpFile : tmpFiles) |
|||
ret.add(tmpFile); |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
/** |
|||
* Files obsoleted by CASSANDRA-7066 : temporary files and compactions_in_progress. We support |
|||
* versions 2.1 (ka) and 2.2 (la). |
|||
* Temporary files have tmp- or tmplink- at the beginning for 2.2 sstables or after ks-cf- for 2.1 sstables |
|||
*/ |
|||
|
|||
private final static String LEGACY_COMP_IN_PROG_REGEX_STR = "^compactions_in_progress(\\-[\\d,a-f]{32})?$"; |
|||
private final static Pattern LEGACY_COMP_IN_PROG_REGEX = Pattern.compile(LEGACY_COMP_IN_PROG_REGEX_STR); |
|||
private final static String LEGACY_TMP_REGEX_STR = "^((.*)\\-(.*)\\-)?tmp(link)?\\-((?:l|k).)\\-(\\d)*\\-(.*)$"; |
|||
private final static Pattern LEGACY_TMP_REGEX = Pattern.compile(LEGACY_TMP_REGEX_STR); |
|||
|
|||
public static boolean isLegacyFile(File file) |
|||
{ |
|||
if (file.isDirectory()) |
|||
return file.getParentFile() != null && |
|||
file.getParentFile().getName().equalsIgnoreCase("system") && |
|||
LEGACY_COMP_IN_PROG_REGEX.matcher(file.getName()).matches(); |
|||
else |
|||
return LEGACY_TMP_REGEX.matcher(file.getName()).matches(); |
|||
} |
|||
|
|||
public static boolean isValidFile(String fileName) |
|||
{ |
|||
return fileName.endsWith(".db") && !LEGACY_TMP_REGEX.matcher(fileName).matches(); |
|||
} |
|||
|
|||
/** |
|||
* @see #fromFilename(File directory, String name) |
|||
* @param filename The SSTable filename |
|||
* @return Descriptor of the SSTable initialized from filename |
|||
*/ |
|||
public static Descriptor fromFilename(String filename) |
|||
{ |
|||
return fromFilename(filename, false); |
|||
} |
|||
|
|||
public static Descriptor fromFilename(String filename, SSTableFormat.Type formatType) |
|||
{ |
|||
return fromFilename(filename).withFormatType(formatType); |
|||
} |
|||
|
|||
public static Descriptor fromFilename(String filename, boolean skipComponent) |
|||
{ |
|||
File file = new File(filename).getAbsoluteFile(); |
|||
return fromFilename(file.getParentFile(), file.getName(), skipComponent).left; |
|||
} |
|||
|
|||
public static Pair<Descriptor, String> fromFilename(File directory, String name) |
|||
{ |
|||
return fromFilename(directory, name, false); |
|||
} |
|||
|
|||
/** |
|||
* Filename of the form is vary by version: |
|||
* |
|||
* <ul> |
|||
* <li><ksname>-<cfname>-(tmp-)?<version>-<gen>-<component> for cassandra 2.0 and before</li> |
|||
* <li>(<tmp marker>-)?<version>-<gen>-<component> for cassandra 3.0 and later</li> |
|||
* </ul> |
|||
* |
|||
* If this is for SSTable of secondary index, directory should ends with index name for 2.1+. |
|||
* |
|||
* @param directory The directory of the SSTable files |
|||
* @param name The name of the SSTable file |
|||
* @param skipComponent true if the name param should not be parsed for a component tag |
|||
* |
|||
* @return A Descriptor for the SSTable, and the Component remainder. |
|||
*/ |
|||
@SuppressWarnings("deprecation") |
|||
public static Pair<Descriptor, String> fromFilename(File directory, String name, boolean skipComponent) |
|||
{ |
|||
File parentDirectory = directory != null ? directory : new File("."); |
|||
|
|||
// tokenize the filename
|
|||
StringTokenizer st = new StringTokenizer(name, String.valueOf(separator)); |
|||
String nexttok; |
|||
|
|||
// read tokens backwards to determine version
|
|||
Deque<String> tokenStack = new ArrayDeque<>(); |
|||
while (st.hasMoreTokens()) |
|||
{ |
|||
tokenStack.push(st.nextToken()); |
|||
} |
|||
|
|||
// component suffix
|
|||
String component = skipComponent ? null : tokenStack.pop(); |
|||
|
|||
nexttok = tokenStack.pop(); |
|||
// generation OR format type
|
|||
SSTableFormat.Type fmt = SSTableFormat.Type.LEGACY; |
|||
if (!CharMatcher.digit().matchesAllOf(nexttok)) |
|||
{ |
|||
fmt = SSTableFormat.Type.validate(nexttok); |
|||
nexttok = tokenStack.pop(); |
|||
} |
|||
|
|||
// generation
|
|||
int generation = Integer.parseInt(nexttok); |
|||
|
|||
// version
|
|||
nexttok = tokenStack.pop(); |
|||
|
|||
if (!Version.validate(nexttok)) |
|||
throw new UnsupportedOperationException("SSTable " + name + " is too old to open. Upgrade to 2.0 first, and run upgradesstables"); |
|||
|
|||
Version version = fmt.info.getVersion(nexttok); |
|||
|
|||
// ks/cf names
|
|||
String ksname, cfname; |
|||
if (version.hasNewFileName()) |
|||
{ |
|||
// for 2.1+ read ks and cf names from directory
|
|||
File cfDirectory = parentDirectory; |
|||
// check if this is secondary index
|
|||
String indexName = ""; |
|||
if (cfDirectory.getName().startsWith(Directories.SECONDARY_INDEX_NAME_SEPARATOR)) |
|||
{ |
|||
indexName = cfDirectory.getName(); |
|||
cfDirectory = cfDirectory.getParentFile(); |
|||
} |
|||
if (cfDirectory.getName().equals(Directories.BACKUPS_SUBDIR)) |
|||
{ |
|||
cfDirectory = cfDirectory.getParentFile(); |
|||
} |
|||
else if (cfDirectory.getParentFile().getName().equals(Directories.SNAPSHOT_SUBDIR)) |
|||
{ |
|||
cfDirectory = cfDirectory.getParentFile().getParentFile(); |
|||
} |
|||
cfname = cfDirectory.getName().split("-")[0] + indexName; |
|||
ksname = cfDirectory.getParentFile().getName(); |
|||
} |
|||
else |
|||
{ |
|||
cfname = tokenStack.pop(); |
|||
ksname = tokenStack.pop(); |
|||
} |
|||
assert tokenStack.isEmpty() : "Invalid file name " + name + " in " + directory; |
|||
|
|||
return Pair.create(new Descriptor(version, parentDirectory, ksname, cfname, generation, fmt, |
|||
// _assume_ version from version
|
|||
Component.digestFor(version.uncompressedChecksumType())), |
|||
component); |
|||
} |
|||
|
|||
@SuppressWarnings("deprecation") |
|||
public IMetadataSerializer getMetadataSerializer() |
|||
{ |
|||
if (version.hasNewStatsFile()) |
|||
return new MetadataSerializer(); |
|||
else |
|||
return new LegacyMetadataSerializer(); |
|||
} |
|||
|
|||
/** |
|||
* @return true if the current Cassandra version can read the given sstable version |
|||
*/ |
|||
public boolean isCompatible() |
|||
{ |
|||
return version.isCompatible(); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() |
|||
{ |
|||
return baseFilename(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object o) |
|||
{ |
|||
if (o == this) |
|||
return true; |
|||
if (!(o instanceof Descriptor)) |
|||
return false; |
|||
Descriptor that = (Descriptor)o; |
|||
return that.directory.equals(this.directory) |
|||
&& that.generation == this.generation |
|||
&& that.ksname.equals(this.ksname) |
|||
&& that.cfname.equals(this.cfname) |
|||
&& that.formatType == this.formatType; |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() |
|||
{ |
|||
return hashCode; |
|||
} |
|||
} |
|||
@ -1,86 +0,0 @@ |
|||
/* |
|||
* Licensed to the Apache Software Foundation (ASF) under one |
|||
* or more contributor license agreements. See the NOTICE file |
|||
* distributed with this work for additional information |
|||
* regarding copyright ownership. The ASF licenses this file |
|||
* to you 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.apache.cassandra.io.sstable.format; |
|||
|
|||
import com.google.common.base.CharMatcher; |
|||
import org.apache.cassandra.config.CFMetaData; |
|||
import org.apache.cassandra.db.RowIndexEntry; |
|||
import org.apache.cassandra.db.SerializationHeader; |
|||
import org.apache.cassandra.io.sstable.format.big.BigFormat; |
|||
|
|||
/** |
|||
* Provides the accessors to data on disk. |
|||
*/ |
|||
public interface SSTableFormat |
|||
{ |
|||
static boolean enableSSTableDevelopmentTestMode = Boolean.getBoolean("cassandra.test.sstableformatdevelopment"); |
|||
|
|||
|
|||
Version getLatestVersion(); |
|||
Version getVersion(String version); |
|||
|
|||
SSTableWriter.Factory getWriterFactory(); |
|||
SSTableReader.Factory getReaderFactory(); |
|||
|
|||
RowIndexEntry.IndexSerializer<?> getIndexSerializer(CFMetaData cfm, Version version, SerializationHeader header); |
|||
|
|||
public static enum Type |
|||
{ |
|||
//Used internally to refer to files with no
|
|||
//format flag in the filename
|
|||
LEGACY("big", BigFormat.instance), |
|||
|
|||
//The original sstable format
|
|||
BIG("big", BigFormat.instance); |
|||
|
|||
public final SSTableFormat info; |
|||
public final String name; |
|||
|
|||
public static Type current() |
|||
{ |
|||
return BIG; |
|||
} |
|||
|
|||
@SuppressWarnings("deprecation") |
|||
private Type(String name, SSTableFormat info) |
|||
{ |
|||
//Since format comes right after generation
|
|||
//we disallow formats with numeric names
|
|||
// We have removed this check for compatibility with the embedded cassandra used for tests.
|
|||
assert !CharMatcher.digit().matchesAllOf(name); |
|||
|
|||
this.name = name; |
|||
this.info = info; |
|||
} |
|||
|
|||
public static Type validate(String name) |
|||
{ |
|||
for (Type valid : Type.values()) |
|||
{ |
|||
//This is used internally for old sstables
|
|||
if (valid == LEGACY) |
|||
continue; |
|||
|
|||
if (valid.name.equalsIgnoreCase(name)) |
|||
return valid; |
|||
} |
|||
|
|||
throw new IllegalArgumentException("No Type constant " + name); |
|||
} |
|||
} |
|||
} |
|||
@ -1,760 +0,0 @@ |
|||
/* |
|||
* Licensed to the Apache Software Foundation (ASF) under one |
|||
* or more contributor license agreements. See the NOTICE file |
|||
* distributed with this work for additional information |
|||
* regarding copyright ownership. The ASF licenses this file |
|||
* to you 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.apache.cassandra.io.util; |
|||
|
|||
import java.io.*; |
|||
import java.nio.ByteBuffer; |
|||
import java.nio.channels.FileChannel; |
|||
import java.nio.charset.Charset; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.*; |
|||
import java.nio.file.attribute.BasicFileAttributes; |
|||
import java.nio.file.attribute.FileAttributeView; |
|||
import java.nio.file.attribute.FileStoreAttributeView; |
|||
import java.text.DecimalFormat; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.atomic.AtomicReference; |
|||
import java.util.function.Consumer; |
|||
import java.util.function.Predicate; |
|||
import java.util.stream.StreamSupport; |
|||
|
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import org.apache.cassandra.concurrent.ScheduledExecutors; |
|||
import org.apache.cassandra.io.FSError; |
|||
import org.apache.cassandra.io.FSErrorHandler; |
|||
import org.apache.cassandra.io.FSReadError; |
|||
import org.apache.cassandra.io.FSWriteError; |
|||
import org.apache.cassandra.io.sstable.CorruptSSTableException; |
|||
import org.apache.cassandra.utils.JVMStabilityInspector; |
|||
|
|||
import static com.google.common.base.Throwables.throwIfUnchecked; |
|||
import static org.apache.cassandra.utils.Throwables.maybeFail; |
|||
import static org.apache.cassandra.utils.Throwables.merge; |
|||
|
|||
public final class FileUtils |
|||
{ |
|||
public static final Charset CHARSET = StandardCharsets.UTF_8; |
|||
|
|||
private static final Logger logger = LoggerFactory.getLogger(FileUtils.class); |
|||
public static final long ONE_KB = 1024; |
|||
public static final long ONE_MB = 1024 * ONE_KB; |
|||
public static final long ONE_GB = 1024 * ONE_MB; |
|||
public static final long ONE_TB = 1024 * ONE_GB; |
|||
|
|||
private static final DecimalFormat df = new DecimalFormat("#.##"); |
|||
public static final boolean isCleanerAvailable = false; |
|||
private static final AtomicReference<Optional<FSErrorHandler>> fsErrorHandler = new AtomicReference<>(Optional.empty()); |
|||
|
|||
public static void createHardLink(String from, String to) |
|||
{ |
|||
createHardLink(new File(from), new File(to)); |
|||
} |
|||
|
|||
public static void createHardLink(File from, File to) |
|||
{ |
|||
if (to.exists()) |
|||
throw new RuntimeException("Tried to create duplicate hard link to " + to); |
|||
if (!from.exists()) |
|||
throw new RuntimeException("Tried to hard link to file that does not exist " + from); |
|||
|
|||
try |
|||
{ |
|||
Files.createLink(to.toPath(), from.toPath()); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new FSWriteError(e, to); |
|||
} |
|||
} |
|||
|
|||
public static File createTempFile(String prefix, String suffix, File directory) |
|||
{ |
|||
try |
|||
{ |
|||
return File.createTempFile(prefix, suffix, directory); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new FSWriteError(e, directory); |
|||
} |
|||
} |
|||
|
|||
public static File createTempFile(String prefix, String suffix) |
|||
{ |
|||
return createTempFile(prefix, suffix, new File(System.getProperty("java.io.tmpdir"))); |
|||
} |
|||
|
|||
public static Throwable deleteWithConfirm(String filePath, boolean expect, Throwable accumulate) |
|||
{ |
|||
return deleteWithConfirm(new File(filePath), expect, accumulate); |
|||
} |
|||
|
|||
public static Throwable deleteWithConfirm(File file, boolean expect, Throwable accumulate) |
|||
{ |
|||
boolean exists = file.exists(); |
|||
assert exists || !expect : "attempted to delete non-existing file " + file.getName(); |
|||
try |
|||
{ |
|||
if (exists) |
|||
Files.delete(file.toPath()); |
|||
} |
|||
catch (Throwable t) |
|||
{ |
|||
try |
|||
{ |
|||
throw new FSWriteError(t, file); |
|||
} |
|||
catch (Throwable t2) |
|||
{ |
|||
accumulate = merge(accumulate, t2); |
|||
} |
|||
} |
|||
return accumulate; |
|||
} |
|||
|
|||
public static void deleteWithConfirm(String file) |
|||
{ |
|||
deleteWithConfirm(new File(file)); |
|||
} |
|||
|
|||
public static void deleteWithConfirm(File file) |
|||
{ |
|||
maybeFail(deleteWithConfirm(file, true, null)); |
|||
} |
|||
|
|||
public static void renameWithOutConfirm(String from, String to) |
|||
{ |
|||
try |
|||
{ |
|||
atomicMoveWithFallback(new File(from).toPath(), new File(to).toPath()); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
if (logger.isTraceEnabled()) |
|||
logger.trace("Could not move file "+from+" to "+to, e); |
|||
} |
|||
} |
|||
|
|||
public static void renameWithConfirm(String from, String to) |
|||
{ |
|||
renameWithConfirm(new File(from), new File(to)); |
|||
} |
|||
|
|||
public static void renameWithConfirm(File from, File to) |
|||
{ |
|||
assert from.exists(); |
|||
if (logger.isTraceEnabled()) |
|||
logger.trace("Renaming {} to {}", from.getPath(), to.getPath()); |
|||
// this is not FSWE because usually when we see it it's because we didn't close the file before renaming it,
|
|||
// and Windows is picky about that.
|
|||
try |
|||
{ |
|||
atomicMoveWithFallback(from.toPath(), to.toPath()); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new RuntimeException(String.format("Failed to rename %s to %s", from.getPath(), to.getPath()), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Move a file atomically, if it fails, it falls back to a non-atomic operation |
|||
* @param from |
|||
* @param to |
|||
* @throws IOException |
|||
*/ |
|||
private static void atomicMoveWithFallback(Path from, Path to) throws IOException |
|||
{ |
|||
try |
|||
{ |
|||
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); |
|||
} |
|||
catch (AtomicMoveNotSupportedException e) |
|||
{ |
|||
logger.trace("Could not do an atomic move", e); |
|||
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); |
|||
} |
|||
|
|||
} |
|||
public static void truncate(String path, long size) |
|||
{ |
|||
try(FileChannel channel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE)) |
|||
{ |
|||
channel.truncate(size); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
public static void closeQuietly(Closeable c) |
|||
{ |
|||
try |
|||
{ |
|||
if (c != null) |
|||
c.close(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
logger.warn("Failed closing {}", c, e); |
|||
} |
|||
} |
|||
|
|||
public static void closeQuietly(AutoCloseable c) |
|||
{ |
|||
try |
|||
{ |
|||
if (c != null) |
|||
c.close(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
logger.warn("Failed closing {}", c, e); |
|||
} |
|||
} |
|||
|
|||
public static void close(Closeable... cs) throws IOException |
|||
{ |
|||
close(Arrays.asList(cs)); |
|||
} |
|||
|
|||
public static void close(Iterable<? extends Closeable> cs) throws IOException |
|||
{ |
|||
Throwable e = null; |
|||
for (Closeable c : cs) |
|||
{ |
|||
try |
|||
{ |
|||
if (c != null) |
|||
c.close(); |
|||
} |
|||
catch (Throwable ex) |
|||
{ |
|||
if (e == null) e = ex; |
|||
else e.addSuppressed(ex); |
|||
logger.warn("Failed closing stream {}", c, ex); |
|||
} |
|||
} |
|||
maybeFail(e, IOException.class); |
|||
} |
|||
|
|||
public static void closeQuietly(Iterable<? extends AutoCloseable> cs) |
|||
{ |
|||
for (AutoCloseable c : cs) |
|||
{ |
|||
try |
|||
{ |
|||
if (c != null) |
|||
c.close(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
logger.warn("Failed closing {}", c, ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static String getCanonicalPath(String filename) |
|||
{ |
|||
try |
|||
{ |
|||
return new File(filename).getCanonicalPath(); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new FSReadError(e, filename); |
|||
} |
|||
} |
|||
|
|||
public static String getCanonicalPath(File file) |
|||
{ |
|||
try |
|||
{ |
|||
return file.getCanonicalPath(); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new FSReadError(e, file); |
|||
} |
|||
} |
|||
|
|||
/** Return true if file is contained in folder */ |
|||
public static boolean isContained(File folder, File file) |
|||
{ |
|||
Path folderPath = Paths.get(getCanonicalPath(folder)); |
|||
Path filePath = Paths.get(getCanonicalPath(file)); |
|||
|
|||
return filePath.startsWith(folderPath); |
|||
} |
|||
|
|||
/** Convert absolute path into a path relative to the base path */ |
|||
public static String getRelativePath(String basePath, String path) |
|||
{ |
|||
try |
|||
{ |
|||
return Paths.get(basePath).relativize(Paths.get(path)).toString(); |
|||
} |
|||
catch(Exception ex) |
|||
{ |
|||
String absDataPath = FileUtils.getCanonicalPath(basePath); |
|||
return Paths.get(absDataPath).relativize(Paths.get(path)).toString(); |
|||
} |
|||
} |
|||
|
|||
public static void clean(ByteBuffer buffer) |
|||
{ |
|||
if (buffer == null) |
|||
return; |
|||
} |
|||
|
|||
public static void createDirectory(String directory) |
|||
{ |
|||
createDirectory(new File(directory)); |
|||
} |
|||
|
|||
public static void createDirectory(File directory) |
|||
{ |
|||
if (!directory.exists()) |
|||
{ |
|||
if (!directory.mkdirs()) |
|||
throw new FSWriteError(new IOException("Failed to mkdirs " + directory), directory); |
|||
} |
|||
} |
|||
|
|||
public static boolean delete(String file) |
|||
{ |
|||
File f = new File(file); |
|||
return f.delete(); |
|||
} |
|||
|
|||
public static void delete(File... files) |
|||
{ |
|||
if (files == null) |
|||
{ |
|||
// CASSANDRA-13389: some callers use Files.listFiles() which, on error, silently returns null
|
|||
logger.debug("Received null list of files to delete"); |
|||
return; |
|||
} |
|||
|
|||
for ( File file : files ) |
|||
{ |
|||
file.delete(); |
|||
} |
|||
} |
|||
|
|||
public static void deleteAsync(final String file) |
|||
{ |
|||
Runnable runnable = new Runnable() |
|||
{ |
|||
public void run() |
|||
{ |
|||
deleteWithConfirm(new File(file)); |
|||
} |
|||
}; |
|||
ScheduledExecutors.nonPeriodicTasks.execute(runnable); |
|||
} |
|||
|
|||
public static void visitDirectory(Path dir, Predicate<? super File> filter, Consumer<? super File> consumer) |
|||
{ |
|||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) |
|||
{ |
|||
StreamSupport.stream(stream.spliterator(), false) |
|||
.map(Path::toFile) |
|||
// stream directories are weakly consistent so we always check if the file still exists
|
|||
.filter(f -> f.exists() && (filter == null || filter.test(f))) |
|||
.forEach(consumer); |
|||
} |
|||
catch (IOException|DirectoryIteratorException ex) |
|||
{ |
|||
logger.error("Failed to list files in {} with exception: {}", dir, ex.getMessage(), ex); |
|||
} |
|||
} |
|||
|
|||
public static String stringifyFileSize(double value) |
|||
{ |
|||
double d; |
|||
if ( value >= ONE_TB ) |
|||
{ |
|||
d = value / ONE_TB; |
|||
String val = df.format(d); |
|||
return val + " TiB"; |
|||
} |
|||
else if ( value >= ONE_GB ) |
|||
{ |
|||
d = value / ONE_GB; |
|||
String val = df.format(d); |
|||
return val + " GiB"; |
|||
} |
|||
else if ( value >= ONE_MB ) |
|||
{ |
|||
d = value / ONE_MB; |
|||
String val = df.format(d); |
|||
return val + " MiB"; |
|||
} |
|||
else if ( value >= ONE_KB ) |
|||
{ |
|||
d = value / ONE_KB; |
|||
String val = df.format(d); |
|||
return val + " KiB"; |
|||
} |
|||
else |
|||
{ |
|||
String val = df.format(value); |
|||
return val + " bytes"; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Deletes all files and subdirectories under "dir". |
|||
* @param dir Directory to be deleted |
|||
* @throws FSWriteError if any part of the tree cannot be deleted |
|||
*/ |
|||
public static void deleteRecursive(File dir) |
|||
{ |
|||
if (dir.isDirectory()) |
|||
{ |
|||
String[] children = dir.list(); |
|||
for (String child : children) |
|||
deleteRecursive(new File(dir, child)); |
|||
} |
|||
|
|||
// The directory is now empty so now it can be smoked
|
|||
deleteWithConfirm(dir); |
|||
} |
|||
|
|||
/** |
|||
* Schedules deletion of all file and subdirectories under "dir" on JVM shutdown. |
|||
* @param dir Directory to be deleted |
|||
*/ |
|||
public static void deleteRecursiveOnExit(File dir) |
|||
{ |
|||
if (dir.isDirectory()) |
|||
{ |
|||
String[] children = dir.list(); |
|||
for (String child : children) |
|||
deleteRecursiveOnExit(new File(dir, child)); |
|||
} |
|||
|
|||
logger.trace("Scheduling deferred deletion of file: {}", dir); |
|||
dir.deleteOnExit(); |
|||
} |
|||
|
|||
public static void handleCorruptSSTable(CorruptSSTableException e) |
|||
{ |
|||
fsErrorHandler.get().ifPresent(handler -> handler.handleCorruptSSTable(e)); |
|||
} |
|||
|
|||
public static void handleFSError(FSError e) |
|||
{ |
|||
fsErrorHandler.get().ifPresent(handler -> handler.handleFSError(e)); |
|||
} |
|||
|
|||
/** |
|||
* handleFSErrorAndPropagate will invoke the disk failure policy error handler, |
|||
* which may or may not stop the daemon or transports. However, if we don't exit, |
|||
* we still want to propagate the exception to the caller in case they have custom |
|||
* exception handling |
|||
* |
|||
* @param e A filesystem error |
|||
*/ |
|||
public static void handleFSErrorAndPropagate(FSError e) |
|||
{ |
|||
JVMStabilityInspector.inspectThrowable(e); |
|||
throwIfUnchecked(e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
/** |
|||
* Get the size of a directory in bytes |
|||
* @param folder The directory for which we need size. |
|||
* @return The size of the directory |
|||
*/ |
|||
public static long folderSize(File folder) |
|||
{ |
|||
final long [] sizeArr = {0L}; |
|||
try |
|||
{ |
|||
Files.walkFileTree(folder.toPath(), new SimpleFileVisitor<Path>() |
|||
{ |
|||
@Override |
|||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) |
|||
{ |
|||
sizeArr[0] += attrs.size(); |
|||
return FileVisitResult.CONTINUE; |
|||
} |
|||
}); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
logger.error("Error while getting {} folder size. {}", folder, e); |
|||
} |
|||
return sizeArr[0]; |
|||
} |
|||
|
|||
public static void copyTo(DataInput in, OutputStream out, int length) throws IOException |
|||
{ |
|||
byte[] buffer = new byte[64 * 1024]; |
|||
int copiedBytes = 0; |
|||
|
|||
while (copiedBytes + buffer.length < length) |
|||
{ |
|||
in.readFully(buffer); |
|||
out.write(buffer); |
|||
copiedBytes += buffer.length; |
|||
} |
|||
|
|||
if (copiedBytes < length) |
|||
{ |
|||
int left = length - copiedBytes; |
|||
in.readFully(buffer, 0, left); |
|||
out.write(buffer, 0, left); |
|||
} |
|||
} |
|||
|
|||
public static boolean isSubDirectory(File parent, File child) throws IOException |
|||
{ |
|||
parent = parent.getCanonicalFile(); |
|||
child = child.getCanonicalFile(); |
|||
|
|||
File toCheck = child; |
|||
while (toCheck != null) |
|||
{ |
|||
if (parent.equals(toCheck)) |
|||
return true; |
|||
toCheck = toCheck.getParentFile(); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public static void append(File file, String ... lines) |
|||
{ |
|||
if (file.exists()) |
|||
write(file, Arrays.asList(lines), StandardOpenOption.APPEND); |
|||
else |
|||
write(file, Arrays.asList(lines), StandardOpenOption.CREATE); |
|||
} |
|||
|
|||
public static void appendAndSync(File file, String ... lines) |
|||
{ |
|||
if (file.exists()) |
|||
write(file, Arrays.asList(lines), StandardOpenOption.APPEND, StandardOpenOption.SYNC); |
|||
else |
|||
write(file, Arrays.asList(lines), StandardOpenOption.CREATE, StandardOpenOption.SYNC); |
|||
} |
|||
|
|||
public static void replace(File file, String ... lines) |
|||
{ |
|||
write(file, Arrays.asList(lines), StandardOpenOption.TRUNCATE_EXISTING); |
|||
} |
|||
|
|||
public static void write(File file, List<String> lines, StandardOpenOption ... options) |
|||
{ |
|||
try |
|||
{ |
|||
Files.write(file.toPath(), |
|||
lines, |
|||
CHARSET, |
|||
options); |
|||
} |
|||
catch (IOException ex) |
|||
{ |
|||
throw new RuntimeException(ex); |
|||
} |
|||
} |
|||
|
|||
public static List<String> readLines(File file) |
|||
{ |
|||
try |
|||
{ |
|||
return Files.readAllLines(file.toPath(), CHARSET); |
|||
} |
|||
catch (IOException ex) |
|||
{ |
|||
if (ex instanceof NoSuchFileException) |
|||
return Collections.emptyList(); |
|||
|
|||
throw new RuntimeException(ex); |
|||
} |
|||
} |
|||
|
|||
public static void setFSErrorHandler(FSErrorHandler handler) |
|||
{ |
|||
fsErrorHandler.getAndSet(Optional.ofNullable(handler)); |
|||
} |
|||
|
|||
/** |
|||
* Returns the size of the specified partition. |
|||
* <p>This method handles large file system by returning {@code Long.MAX_VALUE} if the size overflow. |
|||
* See <a href='https://bugs.openjdk.java.net/browse/JDK-8179320'>JDK-8179320</a> for more information.</p>
|
|||
* |
|||
* @param file the partition |
|||
* @return the size, in bytes, of the partition or {@code 0L} if the abstract pathname does not name a partition |
|||
*/ |
|||
public static long getTotalSpace(File file) |
|||
{ |
|||
return handleLargeFileSystem(file.getTotalSpace()); |
|||
} |
|||
|
|||
/** |
|||
* Returns the number of unallocated bytes on the specified partition. |
|||
* <p>This method handles large file system by returning {@code Long.MAX_VALUE} if the number of unallocated bytes |
|||
* overflow. See <a href='https://bugs.openjdk.java.net/browse/JDK-8179320'>JDK-8179320</a> for more information</p>
|
|||
* |
|||
* @param file the partition |
|||
* @return the number of unallocated bytes on the partition or {@code 0L} |
|||
* if the abstract pathname does not name a partition. |
|||
*/ |
|||
public static long getFreeSpace(File file) |
|||
{ |
|||
return handleLargeFileSystem(file.getFreeSpace()); |
|||
} |
|||
|
|||
/** |
|||
* Returns the number of available bytes on the specified partition. |
|||
* <p>This method handles large file system by returning {@code Long.MAX_VALUE} if the number of available bytes |
|||
* overflow. See <a href='https://bugs.openjdk.java.net/browse/JDK-8179320'>JDK-8179320</a> for more information</p>
|
|||
* |
|||
* @param file the partition |
|||
* @return the number of available bytes on the partition or {@code 0L} |
|||
* if the abstract pathname does not name a partition. |
|||
*/ |
|||
public static long getUsableSpace(File file) |
|||
{ |
|||
return handleLargeFileSystem(file.getUsableSpace()); |
|||
} |
|||
|
|||
/** |
|||
* Returns the {@link FileStore} representing the file store where a file |
|||
* is located. This {@link FileStore} handles large file system by returning {@code Long.MAX_VALUE} |
|||
* from {@code FileStore#getTotalSpace()}, {@code FileStore#getUnallocatedSpace()} and {@code FileStore#getUsableSpace()} |
|||
* it the value is bigger than {@code Long.MAX_VALUE}. See <a href='https://bugs.openjdk.java.net/browse/JDK-8162520'>JDK-8162520</a>
|
|||
* for more information. |
|||
* |
|||
* @param path the path to the file |
|||
* @return the file store where the file is stored |
|||
*/ |
|||
public static FileStore getFileStore(Path path) throws IOException |
|||
{ |
|||
return new SafeFileStore(Files.getFileStore(path)); |
|||
} |
|||
|
|||
/** |
|||
* Handle large file system by returning {@code Long.MAX_VALUE} when the size overflows. |
|||
* @param size returned by the Java's FileStore methods |
|||
* @return the size or {@code Long.MAX_VALUE} if the size was bigger than {@code Long.MAX_VALUE} |
|||
*/ |
|||
private static long handleLargeFileSystem(long size) |
|||
{ |
|||
return size < 0 ? Long.MAX_VALUE : size; |
|||
} |
|||
|
|||
/** |
|||
* Private constructor as the class contains only static methods. |
|||
*/ |
|||
private FileUtils() |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* FileStore decorator used to safely handle large file system. |
|||
* |
|||
* <p>Java's FileStore methods (getTotalSpace/getUnallocatedSpace/getUsableSpace) are limited to reporting bytes as |
|||
* signed long (2^63-1), if the filesystem is any bigger, then the size overflows. {@code SafeFileStore} will |
|||
* return {@code Long.MAX_VALUE} if the size overflow.</p> |
|||
* |
|||
* @see https://bugs.openjdk.java.net/browse/JDK-8162520.
|
|||
*/ |
|||
private static final class SafeFileStore extends FileStore |
|||
{ |
|||
/** |
|||
* The decorated {@code FileStore} |
|||
*/ |
|||
private final FileStore fileStore; |
|||
|
|||
public SafeFileStore(FileStore fileStore) |
|||
{ |
|||
this.fileStore = fileStore; |
|||
} |
|||
|
|||
@Override |
|||
public String name() |
|||
{ |
|||
return fileStore.name(); |
|||
} |
|||
|
|||
@Override |
|||
public String type() |
|||
{ |
|||
return fileStore.type(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean isReadOnly() |
|||
{ |
|||
return fileStore.isReadOnly(); |
|||
} |
|||
|
|||
@Override |
|||
public long getTotalSpace() throws IOException |
|||
{ |
|||
return handleLargeFileSystem(fileStore.getTotalSpace()); |
|||
} |
|||
|
|||
@Override |
|||
public long getUsableSpace() throws IOException |
|||
{ |
|||
return handleLargeFileSystem(fileStore.getUsableSpace()); |
|||
} |
|||
|
|||
@Override |
|||
public long getUnallocatedSpace() throws IOException |
|||
{ |
|||
return handleLargeFileSystem(fileStore.getUnallocatedSpace()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) |
|||
{ |
|||
return fileStore.supportsFileAttributeView(type); |
|||
} |
|||
|
|||
@Override |
|||
public boolean supportsFileAttributeView(String name) |
|||
{ |
|||
return fileStore.supportsFileAttributeView(name); |
|||
} |
|||
|
|||
@Override |
|||
public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) |
|||
{ |
|||
return fileStore.getFileStoreAttributeView(type); |
|||
} |
|||
|
|||
@Override |
|||
public Object getAttribute(String attribute) throws IOException |
|||
{ |
|||
return fileStore.getAttribute(attribute); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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; |
|||
|
|||
import com.github.dockerjava.api.command.InspectContainerResponse; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.IOUtils; |
|||
import org.junit.ClassRule; |
|||
import org.junit.rules.ExternalResource; |
|||
import org.testcontainers.containers.CassandraContainer; |
|||
import org.testcontainers.containers.delegate.CassandraDatabaseDelegate; |
|||
import org.testcontainers.delegate.DatabaseDelegate; |
|||
import org.testcontainers.ext.ScriptUtils; |
|||
|
|||
import javax.script.ScriptException; |
|||
import java.io.IOException; |
|||
import java.net.URL; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.List; |
|||
|
|||
@Slf4j |
|||
public abstract class AbstractNoSqlContainer { |
|||
|
|||
public static final List<String> INIT_SCRIPTS = List.of( |
|||
"cassandra/schema-keyspace.cql", |
|||
"cassandra/schema-ts.cql", |
|||
"cassandra/schema-ts-latest.cql" |
|||
); |
|||
|
|||
@ClassRule(order = 0) |
|||
public static final CassandraContainer cassandra = (CassandraContainer) new CassandraContainer("cassandra:4.1") { |
|||
@Override |
|||
protected void containerIsStarted(InspectContainerResponse containerInfo) { |
|||
super.containerIsStarted(containerInfo); |
|||
DatabaseDelegate db = new CassandraDatabaseDelegate(this); |
|||
INIT_SCRIPTS.forEach(script -> runInitScriptIfRequired(db, script)); |
|||
} |
|||
|
|||
private void runInitScriptIfRequired(DatabaseDelegate db, String initScriptPath) { |
|||
logger().info("Init script [{}]", initScriptPath); |
|||
if (initScriptPath != null) { |
|||
try { |
|||
URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath); |
|||
if (resource == null) { |
|||
logger().warn("Could not load classpath init script: {}", initScriptPath); |
|||
throw new ScriptUtils.ScriptLoadException("Could not load classpath init script: " + initScriptPath + ". Resource not found."); |
|||
} |
|||
String cql = IOUtils.toString(resource, StandardCharsets.UTF_8); |
|||
ScriptUtils.executeDatabaseScript(db, initScriptPath, cql); |
|||
} catch (IOException e) { |
|||
logger().warn("Could not load classpath init script: {}", initScriptPath); |
|||
throw new ScriptUtils.ScriptLoadException("Could not load classpath init script: " + initScriptPath, e); |
|||
} catch (ScriptException e) { |
|||
logger().error("Error while executing init script: {}", initScriptPath, e); |
|||
throw new ScriptUtils.UncategorizedScriptException("Error while executing init script: " + initScriptPath, e); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.withEnv("HEAP_NEWSIZE", "64M") |
|||
.withEnv("MAX_HEAP_SIZE", "512M") |
|||
.withEnv("CASSANDRA_CLUSTER_NAME", "ThingsBoard Cluster"); |
|||
|
|||
@ClassRule(order = 1) |
|||
public static ExternalResource resource = new ExternalResource() { |
|||
@Override |
|||
protected void before() throws Throwable { |
|||
cassandra.start(); |
|||
String cassandraUrl = String.format("%s:%s", cassandra.getHost(), cassandra.getMappedPort(9042)); |
|||
log.debug("Cassandra url [{}]", cassandraUrl); |
|||
System.setProperty("cassandra.url", cassandraUrl); |
|||
} |
|||
|
|||
@Override |
|||
protected void after() { |
|||
cassandra.stop(); |
|||
List.of("cassandra.url") |
|||
.forEach(System.getProperties()::remove); |
|||
} |
|||
}; |
|||
|
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.ClassRule; |
|||
import org.junit.rules.ExternalResource; |
|||
import org.testcontainers.containers.GenericContainer; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Slf4j |
|||
public class AbstractRedisContainer { |
|||
|
|||
@ClassRule(order = 0) |
|||
public static GenericContainer redis = new GenericContainer("redis:7.0") |
|||
.withExposedPorts(6379); |
|||
|
|||
@ClassRule(order = 1) |
|||
public static ExternalResource resource = new ExternalResource() { |
|||
@Override |
|||
protected void before() throws Throwable { |
|||
redis.start(); |
|||
System.setProperty("cache.type", "redis"); |
|||
System.setProperty("redis.connection.type", "standalone"); |
|||
System.setProperty("redis.standalone.host", redis.getHost()); |
|||
System.setProperty("redis.standalone.port", String.valueOf(redis.getMappedPort(6379))); |
|||
} |
|||
|
|||
@Override |
|||
protected void after() { |
|||
redis.stop(); |
|||
List.of("cache.type", "redis.connection.type", "redis.standalone.host", "redis.standalone.port") |
|||
.forEach(System.getProperties()::remove); |
|||
} |
|||
}; |
|||
|
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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; |
|||
|
|||
import com.datastax.oss.driver.api.core.CqlSession; |
|||
import org.cassandraunit.BaseCassandraUnit; |
|||
import org.cassandraunit.CQLDataLoader; |
|||
import org.cassandraunit.dataset.CQLDataSet; |
|||
import org.cassandraunit.utils.EmbeddedCassandraServerHelper; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class CustomCassandraCQLUnit extends BaseCassandraUnit { |
|||
protected List<CQLDataSet> dataSets; |
|||
|
|||
public CqlSession session; |
|||
|
|||
public CustomCassandraCQLUnit(List<CQLDataSet> dataSets) { |
|||
this.dataSets = dataSets; |
|||
} |
|||
|
|||
public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, int readTimeoutMillis) { |
|||
this.dataSets = dataSets; |
|||
this.readTimeoutMillis = readTimeoutMillis; |
|||
} |
|||
|
|||
public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName) { |
|||
this(dataSets); |
|||
this.configurationFileName = configurationFileName; |
|||
} |
|||
|
|||
public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName, int readTimeoutMillis) { |
|||
this(dataSets); |
|||
this.configurationFileName = configurationFileName; |
|||
this.readTimeoutMillis = readTimeoutMillis; |
|||
} |
|||
|
|||
public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName, long startUpTimeoutMillis) { |
|||
super(startUpTimeoutMillis); |
|||
this.dataSets = dataSets; |
|||
this.configurationFileName = configurationFileName; |
|||
} |
|||
|
|||
public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName, long startUpTimeoutMillis, int readTimeoutMillis) { |
|||
super(startUpTimeoutMillis); |
|||
this.dataSets = dataSets; |
|||
this.configurationFileName = configurationFileName; |
|||
this.readTimeoutMillis = readTimeoutMillis; |
|||
} |
|||
|
|||
@Override |
|||
protected void load() { |
|||
session = EmbeddedCassandraServerHelper.getSession(); |
|||
CQLDataLoader dataLoader = new CQLDataLoader(session); |
|||
dataSets.forEach(dataLoader::load); |
|||
session = dataLoader.getSession(); |
|||
System.setSecurityManager(null); |
|||
} |
|||
|
|||
@Override |
|||
protected void after() { |
|||
super.after(); |
|||
try (CqlSession s = session) { |
|||
session = null; |
|||
} |
|||
System.setSecurityManager(null); |
|||
} |
|||
|
|||
// Getters for those who do not like to directly access fields
|
|||
|
|||
public CqlSession getSession() { |
|||
return session; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
# |
|||
# Copyright © 2016-2023 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. |
|||
# |
|||
|
|||
version: '3.0' |
|||
|
|||
services: |
|||
cassandra: |
|||
environment: |
|||
HEAP_NEWSIZE: 128M |
|||
MAX_HEAP_SIZE: 1024M |
|||
@ -0,0 +1,19 @@ |
|||
# |
|||
# Copyright © 2016-2023 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. |
|||
# |
|||
|
|||
version: '3.0' |
|||
|
|||
# Placeholder |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,73 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.rule.engine.credentials; |
|||
|
|||
import org.apache.commons.io.FileUtils; |
|||
import org.junit.Assert; |
|||
import org.junit.Test; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.security.cert.X509Certificate; |
|||
import java.util.List; |
|||
|
|||
public class CertPemCredentialsTest { |
|||
|
|||
private final CertPemCredentials credentials = new CertPemCredentials(); |
|||
|
|||
@Test |
|||
public void testChainOfCertificates() throws Exception { |
|||
String fileContent = fileContent("pem/tb-cloud-chain.pem"); |
|||
|
|||
List<X509Certificate> x509Certificates = credentials.readCertFile(fileContent); |
|||
|
|||
Assert.assertEquals(4, x509Certificates.size()); |
|||
Assert.assertEquals("CN=*.thingsboard.cloud, O=\"ThingsBoard, Inc.\", ST=New York, C=US", |
|||
x509Certificates.get(0).getSubjectDN().getName()); |
|||
Assert.assertEquals("CN=Sectigo ECC Organization Validation Secure Server CA, O=Sectigo Limited, L=Salford, ST=Greater Manchester, C=GB", |
|||
x509Certificates.get(1).getSubjectDN().getName()); |
|||
Assert.assertEquals("CN=USERTrust ECC Certification Authority, O=The USERTRUST Network, L=Jersey City, ST=New Jersey, C=US", |
|||
x509Certificates.get(2).getSubjectDN().getName()); |
|||
Assert.assertEquals("CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB", |
|||
x509Certificates.get(3).getSubjectDN().getName()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSingleCertificate() throws Exception { |
|||
String fileContent = fileContent("pem/tb-cloud.pem"); |
|||
|
|||
List<X509Certificate> x509Certificates = credentials.readCertFile(fileContent); |
|||
|
|||
Assert.assertEquals(1, x509Certificates.size()); |
|||
Assert.assertEquals("CN=*.thingsboard.cloud, O=\"ThingsBoard, Inc.\", ST=New York, C=US", |
|||
x509Certificates.get(0).getSubjectDN().getName()); |
|||
} |
|||
|
|||
@Test |
|||
public void testEmptyFileContent() throws Exception { |
|||
String fileContent = fileContent("pem/empty.pem"); |
|||
|
|||
List<X509Certificate> x509Certificates = credentials.readCertFile(fileContent); |
|||
|
|||
Assert.assertEquals(0, x509Certificates.size()); |
|||
} |
|||
|
|||
private String fileContent(String fileName) throws IOException { |
|||
ClassLoader classLoader = getClass().getClassLoader(); |
|||
File file = new File(classLoader.getResource(fileName).getFile()); |
|||
return FileUtils.readFileToString(file, "UTF-8"); |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIFejCCBSCgAwIBAgIQT2YV5NVp2PAY1O5rxMlj6DAKBggqhkjOPQQDAjCBlTEL |
|||
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE |
|||
BxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMT0wOwYDVQQDEzRT |
|||
ZWN0aWdvIEVDQyBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVy |
|||
IENBMB4XDTIxMTAwNTAwMDAwMFoXDTIyMTAwNTIzNTk1OVowWjELMAkGA1UEBhMC |
|||
VVMxETAPBgNVBAgTCE5ldyBZb3JrMRowGAYDVQQKExFUaGluZ3NCb2FyZCwgSW5j |
|||
LjEcMBoGA1UEAwwTKi50aGluZ3Nib2FyZC5jbG91ZDB2MBAGByqGSM49AgEGBSuB |
|||
BAAiA2IABNkK/UerQZXPP0H/Tl8YhRPlzW85yTAcQ5hXhs2fyXn7Bdj4EueQuZrv |
|||
Pw98xwHJr87jslFbS/WiSdtBYPvjsUyXqh7aMvOcEhSgEOWDmtoj3P1Xk1hNLb6m |
|||
xAQfFL8cZ6OCA20wggNpMB8GA1UdIwQYMBaAFE1K78RGsxKtT06asVniUasIEHgI |
|||
MB0GA1UdDgQWBBRr4HG23dsao68r9r7obGxB70ptBzAOBgNVHQ8BAf8EBAMCB4Aw |
|||
DAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwSgYD |
|||
VR0gBEMwQTA1BgwrBgEEAbIxAQIBAwQwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9z |
|||
ZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQICMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 |
|||
Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb0VDQ09yZ2FuaXphdGlvblZhbGlkYXRp |
|||
b25TZWN1cmVTZXJ2ZXJDQS5jcmwwgYoGCCsGAQUFBwEBBH4wfDBVBggrBgEFBQcw |
|||
AoZJaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvRUNDT3JnYW5pemF0aW9u |
|||
VmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAjBggrBgEFBQcwAYYXaHR0cDov |
|||
L29jc3Auc2VjdGlnby5jb20wMQYDVR0RBCowKIITKi50aGluZ3Nib2FyZC5jbG91 |
|||
ZIIRdGhpbmdzYm9hcmQuY2xvdWQwggGABgorBgEEAdZ5AgQCBIIBcASCAWwBagB2 |
|||
AEalVet1+pEgMLWiiWn0830RLEF0vv1JuIWr8vxw/m1HAAABfFDbhtMAAAQDAEcw |
|||
RQIhAKNykhkQTngK0yOcYHGHUQSy6JmJYl+5nc1qELirPHwAAiBgV2Db5ZFHNvzn |
|||
zp9Ob/OG0o36z6rcilbLI/daZwnyewB3AEHIyrHfIkZKEMahOglCh15OMYsbA+vr |
|||
S8do8JBilgb2AAABfFDbhqYAAAQDAEgwRgIhALFvTbapKhO7DPrF6KtE9sjFMMth |
|||
qjqaeaHYN6JGnUAIAiEApEW+rxlzxH1+qEwJrFyQLr5rSKTuEoSjv3hbrzb9GQ4A |
|||
dwApeb7wnjk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAXxQ24ZnAAAEAwBI |
|||
MEYCIQDReSpJPzADl/fBdCvyZwWu3Ubi6y0h/S4i7RIjf5L5pAIhAJ1oUmNmRWQL |
|||
1U3HAqz2V8ckH/rgA0BR+CkGBigt3dfwMAoGCCqGSM49BAMCA0gAMEUCID0Wv3hJ |
|||
GyO4kxlCsA/Kruzew8Wr/0k84csyaCo0k16kAiEAtAobCIzx/PIDWU2rX5elBNiR |
|||
13sr0/ED+2PUom2dnfg= |
|||
-----END CERTIFICATE----- |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIDrjCCAzOgAwIBAgIQNb50Y4yz6d4oBXC3l4CzZzAKBggqhkjOPQQDAzCBiDEL |
|||
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl |
|||
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT |
|||
JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgxMTAy |
|||
MDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBlTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT |
|||
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMP |
|||
U2VjdGlnbyBMaW1pdGVkMT0wOwYDVQQDEzRTZWN0aWdvIEVDQyBPcmdhbml6YXRp |
|||
b24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMFkwEwYHKoZIzj0CAQYIKoZI |
|||
zj0DAQcDQgAEnI5cCmFvoVij0NXO+vxE+f+6Bh57FhpyH0LTCrJmzfsPSXIhTSex |
|||
r92HOlz+aHqoGE0vSe/CSwLFoWcZ8W1jOaOCAW4wggFqMB8GA1UdIwQYMBaAFDrh |
|||
CYbUzxnClnZ0SXbc4DXGY2OaMB0GA1UdDgQWBBRNSu/ERrMSrU9OmrFZ4lGrCBB4 |
|||
CDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAU |
|||
BggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAEC |
|||
AjBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNF |
|||
UlRydXN0RUNDQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEE |
|||
ajBoMD8GCCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRy |
|||
dXN0RUNDQWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVz |
|||
ZXJ0cnVzdC5jb20wCgYIKoZIzj0EAwMDaQAwZgIxAOk//uo7i/MoeKdcyeqvjOXs |
|||
BJFGLI+1i0d+Tty7zEnn2w4DNS21TK8wmY3Kjm3EmQIxAPI1qHM/I+OS+hx0OZhG |
|||
fDoNifTe/GxgWZ1gOYQKzn6lwP0yGKlrP+7vrVC8IczJ4A== |
|||
-----END CERTIFICATE----- |
|||
-----BEGIN CERTIFICATE----- |
|||
MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7 |
|||
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD |
|||
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE |
|||
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4 |
|||
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5 |
|||
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO |
|||
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0 |
|||
aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL |
|||
q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc |
|||
JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA |
|||
FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1 |
|||
xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI |
|||
MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j |
|||
b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG |
|||
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM |
|||
BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy |
|||
ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+ |
|||
FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV |
|||
bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY |
|||
CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf |
|||
8qn0dNW44bOwgeThpWOjzOoEeJBuv/c= |
|||
-----END CERTIFICATE----- |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb |
|||
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow |
|||
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj |
|||
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL |
|||
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE |
|||
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM |
|||
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP |
|||
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua |
|||
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe |
|||
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 |
|||
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR |
|||
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm |
|||
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU |
|||
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF |
|||
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v |
|||
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t |
|||
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF |
|||
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q |
|||
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz |
|||
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 |
|||
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi |
|||
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 |
|||
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== |
|||
-----END CERTIFICATE----- |
|||
|
|||
@ -0,0 +1,32 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIFejCCBSCgAwIBAgIQT2YV5NVp2PAY1O5rxMlj6DAKBggqhkjOPQQDAjCBlTEL |
|||
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE |
|||
BxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMT0wOwYDVQQDEzRT |
|||
ZWN0aWdvIEVDQyBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVy |
|||
IENBMB4XDTIxMTAwNTAwMDAwMFoXDTIyMTAwNTIzNTk1OVowWjELMAkGA1UEBhMC |
|||
VVMxETAPBgNVBAgTCE5ldyBZb3JrMRowGAYDVQQKExFUaGluZ3NCb2FyZCwgSW5j |
|||
LjEcMBoGA1UEAwwTKi50aGluZ3Nib2FyZC5jbG91ZDB2MBAGByqGSM49AgEGBSuB |
|||
BAAiA2IABNkK/UerQZXPP0H/Tl8YhRPlzW85yTAcQ5hXhs2fyXn7Bdj4EueQuZrv |
|||
Pw98xwHJr87jslFbS/WiSdtBYPvjsUyXqh7aMvOcEhSgEOWDmtoj3P1Xk1hNLb6m |
|||
xAQfFL8cZ6OCA20wggNpMB8GA1UdIwQYMBaAFE1K78RGsxKtT06asVniUasIEHgI |
|||
MB0GA1UdDgQWBBRr4HG23dsao68r9r7obGxB70ptBzAOBgNVHQ8BAf8EBAMCB4Aw |
|||
DAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwSgYD |
|||
VR0gBEMwQTA1BgwrBgEEAbIxAQIBAwQwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9z |
|||
ZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQICMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 |
|||
Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb0VDQ09yZ2FuaXphdGlvblZhbGlkYXRp |
|||
b25TZWN1cmVTZXJ2ZXJDQS5jcmwwgYoGCCsGAQUFBwEBBH4wfDBVBggrBgEFBQcw |
|||
AoZJaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvRUNDT3JnYW5pemF0aW9u |
|||
VmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAjBggrBgEFBQcwAYYXaHR0cDov |
|||
L29jc3Auc2VjdGlnby5jb20wMQYDVR0RBCowKIITKi50aGluZ3Nib2FyZC5jbG91 |
|||
ZIIRdGhpbmdzYm9hcmQuY2xvdWQwggGABgorBgEEAdZ5AgQCBIIBcASCAWwBagB2 |
|||
AEalVet1+pEgMLWiiWn0830RLEF0vv1JuIWr8vxw/m1HAAABfFDbhtMAAAQDAEcw |
|||
RQIhAKNykhkQTngK0yOcYHGHUQSy6JmJYl+5nc1qELirPHwAAiBgV2Db5ZFHNvzn |
|||
zp9Ob/OG0o36z6rcilbLI/daZwnyewB3AEHIyrHfIkZKEMahOglCh15OMYsbA+vr |
|||
S8do8JBilgb2AAABfFDbhqYAAAQDAEgwRgIhALFvTbapKhO7DPrF6KtE9sjFMMth |
|||
qjqaeaHYN6JGnUAIAiEApEW+rxlzxH1+qEwJrFyQLr5rSKTuEoSjv3hbrzb9GQ4A |
|||
dwApeb7wnjk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAXxQ24ZnAAAEAwBI |
|||
MEYCIQDReSpJPzADl/fBdCvyZwWu3Ubi6y0h/S4i7RIjf5L5pAIhAJ1oUmNmRWQL |
|||
1U3HAqz2V8ckH/rgA0BR+CkGBigt3dfwMAoGCCqGSM49BAMCA0gAMEUCID0Wv3hJ |
|||
GyO4kxlCsA/Kruzew8Wr/0k84csyaCo0k16kAiEAtAobCIzx/PIDWU2rX5elBNiR |
|||
13sr0/ED+2PUom2dnfg= |
|||
-----END CERTIFICATE----- |
|||
@ -1,16 +0,0 @@ |
|||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. |
|||
# For additional information regarding the format and rule options, please see: |
|||
# https://github.com/browserslist/browserslist#queries |
|||
|
|||
# For the full list of supported browsers by the Angular framework, please see: |
|||
# https://angular.io/guide/browser-support |
|||
|
|||
# You can see what browsers were selected by your queries by running: |
|||
# npx browserslist |
|||
|
|||
last 1 Chrome version |
|||
last 1 Firefox version |
|||
last 2 Edge major versions |
|||
last 2 Safari major versions |
|||
last 2 iOS major versions |
|||
Firefox ESR |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue