Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix read file in jar 20190402 dq #646

Merged
merged 13 commits into from
Apr 15, 2019
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.alibaba.csp.sentinel.demo.file.rule;

import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.FileInJarReadableDataSource;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.Rule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;

import java.util.List;

/**
* <p>
* This Demo shows how to use {@link FileInJarReadableDataSource} to read {@link Rule}s from jarfile. The
* {@link FileInJarReadableDataSource} will automatically fetches the backend file every 3 seconds, and
* inform the listener if the file is updated.
* </p>
* <p>
* Each {@link ReadableDataSource} has a {@link SentinelProperty} to hold the deserialized config data.
* {@link PropertyListener} will listen to the {@link SentinelProperty} instead of the datasource.
* {@link Converter} is used for telling how to deserialize the data.
* </p>
* <p>
* {@link FlowRuleManager#register2Property(SentinelProperty)},
* {@link DegradeRuleManager#register2Property(SentinelProperty)},
* {@link SystemRuleManager#register2Property(SentinelProperty)} could be called for listening the
* {@link Rule}s change.
* </p>
* <p>
* For other kinds of data source, such as <a href="https://github.com/alibaba/nacos">Nacos</a>,
* Zookeeper, Git, or even CSV file, We could implement {@link ReadableDataSource} interface to read these
* configs.
* </p>
*
* @author dingq
* @date 2019-03-30
*/
public class JarFileDataSourceDemo {
public static void main(String[] args) throws Exception {
JarFileDataSourceDemo demo = new JarFileDataSourceDemo();
demo.listenRules();

/*
* Start to require tokens, rate will be limited by rule in FlowRule.json
*/
FlowQpsRunner runner = new FlowQpsRunner();
runner.simulateTraffic();
runner.tick();
}

private void listenRules() throws Exception {
String jarPath = System.getProperty("user.dir") + "/sentinel-demo/sentinel-demo-dynamic-file-rule/target/sentinel-demo-dynamic-file-rule-1.5.1-SNAPSHOT.jar";
// eg: if flowRuleInJarName full path is 'sentinel-demo-dynamic-file-rule-1.5.1-SNAPSHOT.jar!/classes/FlowRule.json',
// your flowRuleInJarName is 'classes/FlowRule.json'
String flowRuleInJarPath = "FlowRule.json";

FileInJarReadableDataSource<List<FlowRule>> flowRuleDataSource = new FileInJarReadableDataSource<>(
jarPath,flowRuleInJarPath, flowRuleListParser);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}

private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source,
new TypeReference<List<FlowRule>>() {});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.csp.sentinel.datasource;

import com.alibaba.csp.sentinel.log.RecordLog;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* <p>
* A {@link ReadableDataSource} based on jarfile. This class can only read file when it
* run but will not automatically refresh if it is changed.
* </p>
* <p>
* Limitations: Default read buffer size is 1 MB. If file size is greater than
* buffer size, exceeding bytes will be ignored. Default charset is UTF-8.
* </p>
*
* @author dingq
* @date 2019-03-30
*/
public class FileInJarReadableDataSource<T> extends AutoRefreshDataSource<String, T> {
private static final int MAX_SIZE = 1024 * 1024 * 4;
private static final long DEFAULT_REFRESH_MS = 3000;
private static final int DEFAULT_BUF_SIZE = 1024 * 1024;
private static final Charset DEFAULT_CHAR_SET = Charset.forName("utf-8");

private byte[] buf;
private JarEntry jarEntry;
private JarFile jarFile;
private final Charset charset;
private final String jarName;
private final String fileInJarName;

/**
* @param jarName the jar to read
* @param fileInJarName the file in jar to read
* @param configParser the config decoder (parser)
* @throws FileNotFoundException
*/
public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser)
throws IOException {
this(jarName, fileInJarName, configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, DEFAULT_CHAR_SET);
}

public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser, int bufSize)
throws IOException {
this(jarName, fileInJarName, configParser, DEFAULT_REFRESH_MS, bufSize, DEFAULT_CHAR_SET);
}

public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser, Charset charset)
throws IOException {
this(jarName, fileInJarName, configParser, DEFAULT_REFRESH_MS, DEFAULT_BUF_SIZE, charset);
}

public FileInJarReadableDataSource(String jarName, String fileInJarName, Converter<String, T> configParser, long recommendRefreshMs, int bufSize,
Charset charset) throws IOException {
super(configParser, recommendRefreshMs);
if (bufSize <= 0 || bufSize > MAX_SIZE) {
throw new IllegalArgumentException("bufSize must between (0, " + MAX_SIZE + "], but " + bufSize + " get");
}
if (charset == null) {
throw new IllegalArgumentException("charset can't be null");
}
this.buf = new byte[bufSize];
this.charset = charset;
this.jarName = jarName;
this.fileInJarName = fileInJarName;
refreshJar();
firstLoad();
}

@Override
public String readSource() throws Exception {
if (null == jarEntry) {
// Will throw FileNotFoundException later.
RecordLog.warn(String.format("[FileInJarReadableDataSource] File does not exist: %s", jarFile.getName()));
}
InputStream inputStream = null;
try {
inputStream = jarFile.getInputStream(jarEntry);
if (inputStream.available() > buf.length) {
throw new IllegalStateException(jarFile.getName() + " file size=" + inputStream.available()
+ ", is bigger than bufSize=" + buf.length + ". Can't read");
}
int len = inputStream.read(buf);
return new String(buf, 0, len, charset);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception ignore) {
}
}
}

}

@Override
protected boolean isModified() {
return false;
}

private void firstLoad() {
try {
T newValue = loadConfig();
getProperty().updateValue(newValue);
} catch (Throwable e) {
RecordLog.info("loadConfig exception", e);
}
}

@Override
public void close() throws Exception {
super.close();
buf = null;
}

private void refreshJar() throws IOException {
this.jarFile = new JarFile(jarName);
this.jarEntry = jarFile.getJarEntry(fileInJarName);
}
}