【仅供内部供应商使用,不提供对外解答和培训】

Page tree

【仅供内部供应商使用,不提供对外解答和培训】

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Current »

一、特殊名词介绍

二、背景、场景介绍

在集群场景或非集群但多报表系统共用一套模板和相关配置文件的场景中。决策系统提供了FTP和SFTP两种文件服务器用于实现将系统运行中的文件性质的资源进行统一的管理。

包括 assets、reportlets、resources、schedule、dashboards、treasures、backup 这些文件夹下的资源都会从文件服务器指向的资源地址进行读取。而ResourceRepositoryFactoryProvider接口就是用于,用户有其他类型的统一文件服务器的场景中,允许用户进行文件服务器的扩展,比如OSS、HDFS之类的。

三、接口介绍

ResourceRepositoryFactoryProvider.java
package com.fr.io.fun;

import com.fr.io.base.provider.RepositoryFactoryProvider;
import com.fr.io.config.RepositoryConfig;
import com.fr.stable.fun.mark.Mutable;
import com.fr.stable.script.CalculatorKey;

/**
 * 仓库工厂插件接口
 * <p>
 * Created by rinoux on 2018/3/13.
 */
public interface ResourceRepositoryFactoryProvider<T extends RepositoryConfig> extends Mutable {

    CalculatorKey KEY = CalculatorKey.createKey(ResourceRepositoryFactoryProvider.class.getName());

    String MARK_STRING = "ResourceRepositoryFactoryProvider";

    RepositoryFactoryProvider<T> getFactory();
}
RepositoryConfig.java
package com.fr.io.config;

import com.fr.common.annotations.Open;
import com.fr.config.utils.UniqueKey;
import com.fr.io.base.provider.Updatable;
import com.fr.third.fasterxml.jackson.annotation.JsonIgnoreProperties;

/**
 * Created by rinoux on 2018/5/15.
 */
@JsonIgnoreProperties({"id", "data", "classInfo", "nameSpace"})
@Open
public abstract class RepositoryConfig extends UniqueKey implements Updatable {

    private String identity;

    public RepositoryConfig() {}

    public RepositoryConfig(String identity) {
        this.identity = identity;
    }


    public String getIdentity() {
        return identity;
    }

    public void setIdentity(String identity) {
        this.identity = identity;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        RepositoryConfig cloned = (RepositoryConfig) super.clone();
        cloned.setIdentity(this.identity);

        return cloned;
    }
}

RepositoryFactoryProvider.java
package com.fr.io.base.provider;

import com.fr.common.annotations.Open;
import com.fr.io.config.RepositoryConfigManagerProvider;
import com.fr.io.config.RepositoryConfig;
import com.fr.io.context.info.RepositoryProfile;
import com.fr.io.repository.ResourceRepository;

import java.io.Serializable;

/**
 * Created by rinoux on 2018/3/20.
 */
@Open
public interface RepositoryFactoryProvider<T extends RepositoryConfig> extends Serializable {

    /**
     * 仓库标识
     *
     * @return 标识
     */
    String getIdentity();


    /**
     * 获取配置管理
     *
     * @return 配置管理器
     */
    RepositoryConfigManagerProvider<T> getConfigManager();


    /**
     * profile的类
     *
     * @return
     */
    Class<? extends RepositoryProfile<T>> getProfileClass();


    /**
     * 配置类型
     *
     * @return
     */
    Class<T> getConfigClass();


    /**
     * 验证配置是否正确
     *
     * @param config 配置
     * @return 是否正确
     */
    boolean verifyConfig(T config);


    /**
     * 生产仓库
     *
     * @param repoName 仓库名称
     * @param workRoot 工作路径
     * @param config   所需配置
     * @return 仓库
     */
    ResourceRepository produce(String repoName, String workRoot, T config);


    /**
     * 生产仓库
     *
     * @param repoName 仓库名称
     * @param workRoot 工作路径
     * @return 仓库
     */
    ResourceRepository produce(String repoName, String workRoot);
}
RepositoryInfoProvider.java
package com.fr.io.base.provider;

/**
 * Created by rinoux on 2018/3/8.
 */
public interface RepositoryInfoProvider {

    /**
     * 实际仓库名称
     *
     * @return 名称
     */
    String getRepoName();

    /**
     * 设置仓库名称
     *
     * @param repoName 名称
     */
    void setRepoName(String repoName);

    /**
     * 仓库的类型标识(FTP,OSS之类的)
     *
     * @return getIdentity
     */
    String getIdentity();


    /**
     * 获取工作路径
     *
     * @return workRoot
     */
    String getWorkRoot();


    /**
     * 文件大小和实际磁盘占用大小一致
     *
     *
     * @return 是否一致
     */
    boolean isAccurateDiskSize();
}

WorkResource.java
package com.fr.workspace.resource;

import com.fr.stable.Filter;
import com.fr.workspace.base.WorkspaceAPI;
import com.fr.workspace.base.WorkspaceConstants;
import org.jetbrains.annotations.Nullable;

import java.io.InputStream;

/**
 * Created by juhaoyu on 2018/6/14.
 * 工作空间资源读写
 */
@WorkspaceAPI(description = "Fine-Core_Workspace_Description_Of_Work_Resource")
public interface WorkResource {

    /**
     * 读取资源(相对工作目录的资源路径)
     */
    byte[] readFully(String path);


    /**
     * 打开资源流
     *
     * @param path 路径
     * @return 不存在返回null
     */
    @Nullable
    InputStream openStream(String path);

    /**
     * 写资源
     */
    void write(String path, byte[] data);

    /**
     * 创建空文件
     * 已存在的文件不会清空
     */
    boolean createFile(String path);

    /**
     * 创建目录
     */
    boolean createDirectory(String path);

    /**
     * 删除文件或目录
     */
    boolean delete(String path);

    /**
     * 重命名文件或目录
     */
    boolean rename(String from, String to);

    /**
     * 判断文件或目录是否存在
     */
    boolean exist(String path);


    /**
     * 列出路径下的所有name
     *
     * @param dir
     * @return
     */
    String[] list(String dir);


    /**
     * 列出路径下的所有符合条件的name
     *
     * @param dir       路径
     * @param filter 过滤器
     * @return
     */
    String[] list(String dir, Filter<String> filter);


    /**
     * 是否是文件夹
     *
     * @param path
     * @return
     */
    boolean isDirectory(String path);



    /**
     * 最后修改时间
     *
     * @param path 路径
     * @return mills
     */
    long lastModified(String path);


    /**
     * 文件大小
     *
     * @param path 文件路径
     * @return 大小
     */
    long length(String path);

    /**
     * 融合了{@link this#write(String, byte[])}, {@link this#delete(String)} 和 {@link this#rename(String, String)} 三个操作
     *
     * @param tmpPath tmp路径
     * @param path path路径
     * @param content 文件内容
     * @return 保存是否成功
     */
    default void save(String tmpPath, String path, byte[] content) {
        // 输出临时文件到服务器
        write(tmpPath, content);
        // 删除源文件
        delete(path);
        // 重命名文件
        rename(tmpPath, path);
    }
}

ResourceRepository.java
package com.fr.io.repository;


import com.fr.io.base.provider.RepositoryInfoProvider;
import com.fr.stable.Filter;
import com.fr.workspace.resource.ResourceIOException;
import com.fr.workspace.resource.WorkResource;

import java.io.InputStream;
import java.net.URL;

/**
 * 资源仓库.
 */
public interface ResourceRepository extends RepositoryInfoProvider, WorkResource {

    /**
     * 存储的分隔符
     *
     * @return 分隔符
     */
    String getSeparator();


    /**
     * 获取文件的entry
     *
     * @param path 路径
     * @return FineFileEntry 不存在返回null
     */
    FineFileEntry getEntry(String path);


    /**
     * 列出路径下所有的FineFileEntry
     *
     * @param dir 路径
     * @return 所有FineFileEntry,不存在或者非目录返回空数组
     */
    FineFileEntry[] listEntry(String dir);

    /**
     * 获取url
     *
     * @param path 路径
     * @return url
     */
    URL getResource(String path);

    /**
     * 根据路径读取文件流
     * <p>
     * InputStream的关闭应该由client来负责
     *
     * @param file 路径
     * @return 数据字节数组 如果文件不存在返回null
     * @throws ResourceIOException read过程异常时抛出异常
     */
    InputStream read(String file) throws ResourceIOException;

    /**
     * 列出该路径下的所有filter接受的内容
     * <p>
     * filter为null时默认设置为DefaultFilter,表示接受所有类型
     * <p>
     * 返回的List为dir下所有filter接受的文件和文件夹的名称name,而非全路径path
     * <p>
     * 非递归
     *
     * @param dir    路径
     * @param filter 过滤器
     * @return 所有文件的名称 路径不存在时返回空的数组
     */
    @Override
    String[] list(String dir, Filter<String> filter);

    /**
     * 列出该路径下的所有文件和文件夹
     * <p>
     * 返回的List为dir下所有filter接受的文件和文件夹的名称name,而非全路径path
     * <p>
     * 非递归
     *
     * @param dir 路径
     * @return 所有文件的名称
     */
    @Override
    String[] list(String dir);

    /**
     * 向文件写入数据
     * <p>
     * 如果文件不存在则新建文件
     * <p>
     * data使用后需要在write中关闭
     *
     * @param file 文件path
     * @param data 数据
     * @throws ResourceIOException 写过程中的异常抛出
     */
    void write(String file, InputStream data) throws ResourceIOException;

    /**
     * 追加写文件
     * <p>
     * 向一个已经存在的文件末尾追加新的内容
     *
     * @param file 名称
     * @param data 数据流
     * @throws ResourceIOException 追加写的过程中发生异常抛出
     */
    void appendWrite(String file, InputStream data) throws ResourceIOException;

    /**
     * 追加写文件
     * <p>
     * 向一个已经存在的文件末尾追加新的内容
     *
     * @param file 名称
     * @param data 数据流
     * @throws ResourceIOException 追加写的过程中发生异常抛出
     */
    void appendWrite(String file, byte[] data) throws ResourceIOException;

    /**
     * 复制
     * <p>
     * origPath和desPath均为全路径
     * <p>
     * 若desPath已经存在返回false
     *
     * @param origPath 源
     * @param desPath  目标
     * @return 是否copy成功
     * @throws ResourceIOException 重命名过程中出现的异常抛出
     */
    boolean copy(String origPath, String desPath) throws ResourceIOException;

    /**
     * 是否为目录
     * <p>
     * 对于对象存储,key以"/"结尾的判断为目录
     *
     * @param path 名称
     * @return 是否是目录
     */
    @Override
    boolean isDirectory(String path);



    /**
     * 关闭
     * <p>
     * 做一些关闭的操作
     */
    void shutDown();

    /**
     * 认证
     *
     * @return 默认为<code>true</code>
     */
    default boolean authentication(){
        return true;
    }


}

RepositoryProfile.java
package com.fr.io.context.info;

import com.fr.cluster.ClusterBridge;
import com.fr.common.annotations.Open;
import com.fr.config.Identifier;
import com.fr.config.holder.Conf;
import com.fr.config.holder.factory.Holders;
import com.fr.config.holder.impl.MapConf;
import com.fr.config.utils.UniqueKey;
import com.fr.io.base.exception.RepositoryException;
import com.fr.io.base.provider.RepositoryFactoryProvider;
import com.fr.io.base.provider.Updatable;
import com.fr.io.config.RepositoryConfig;
import com.fr.io.context.ResourceModuleContext;
import com.fr.stable.StringUtils;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Created by rinoux on 2018/3/26.
 */
@SuppressWarnings("all")
@Open
public abstract class RepositoryProfile<T extends RepositoryConfig> extends UniqueKey implements Serializable, Updatable {

    /**
     * 仓库类型
     */
    @Identifier("identity")
    private Conf<String> identity = Holders.simple(StringUtils.EMPTY);

    /**
     * 仓库名称
     */
    @Identifier("repoName")
    private Conf<String> repoName = Holders.simple(StringUtils.EMPTY);
    /**
     * 是否集群共享
     */
    @Identifier("shared")
    private Conf<Boolean> shared = Holders.simple(Boolean.FALSE);
    /**
     * 各个机器上的工作路径,如果是共享就共用一个,非共享则各自有各自的workroot,即[id, workroot]
     */
    @Identifier("workroots")
    private MapConf<Map<String, String>> workroots = Holders.map(new HashMap<String, String>(), String.class, String.class);


    public RepositoryProfile() {
    }

    public RepositoryProfile(String identity, String repoName, String workRoot) {
        init(identity, repoName, workRoot, false);
    }


    public RepositoryProfile(String identity, String repoName, String workRoot, T config) {
        init(identity, repoName, workRoot, false);
    }

    public RepositoryProfile(String identity, String repoName, String workRoot, boolean shared) {
        init(identity, repoName, workRoot, shared);
    }


    public RepositoryProfile(String identity, String repoName, String workRoot, boolean shared, T config) {
        init(identity, repoName, workRoot, shared);
    }

    private void init(String identity, String repoName, String workRoot, boolean shared) {
        this.identity.set(identity);
        this.repoName.set(repoName);
        this.shared.set(shared);
        this.setWorkRoot(workRoot);
    }


    public String getIdentity() {
        return identity.get();
    }

    public void setIdentity(String identity) {
        this.identity.set(identity);
    }

    public String getRepoName() {
        return repoName.get();
    }

    public void setRepoName(String repoName) {
        this.repoName.set(repoName);
    }

    public String getWorkRoot() {
        String currentNodeId = ClusterBridge.getView().getCurrent().getID();
        if (workroots.containsKey(currentNodeId)) {
            return (String) workroots.get(currentNodeId);
        }
        else if (isShared()) {
            Iterator<String> iterator = workroots.get().values().iterator();
            while (iterator.hasNext()) {
                return iterator.next();
            }
        }

        return null;
    }

    public void setWorkRoot(String workRoot) {
        String currentNodeId = ClusterBridge.getView().getCurrent().getID();
        if (isShared()) {
            // 共享时改所有的 workroots
            Set<String> ids = workroots.get().keySet();
            for (String id : ids) {
                workroots.put(id, workRoot);
            }
        }
        // 改自己的
        this.workroots.put(currentNodeId, workRoot);
    }

    public boolean isShared() {
        return shared.get();
    }

    public void setShared(boolean shared) {
        this.shared.set(shared);
    }

    public abstract T getConfig();

    public abstract void setConfig(T config);



    @Override
    public RepositoryProfile<T> clone() throws CloneNotSupportedException {
        RepositoryProfile<T> cloned = (RepositoryProfile<T>) super.clone();
        cloned.identity = (Conf<String>) this.identity.clone();
        cloned.repoName = (Conf<String>) this.repoName.clone();
        cloned.shared = (Conf<Boolean>) this.shared.clone();
        cloned.workroots = (MapConf<Map<String, String>>) this.workroots.clone();

        return cloned;
    }

    /**
     * 安装并返回生产的安装组件
     *
     * @return 仓库安装组件
     * @throws RepositoryException
     */
    public InstalledComponent<T> install() {
        return ResourceModuleContext.install(this);
    }


    private boolean validate() {
        if (StringUtils.isEmpty(getIdentity())) {
            return false;
        }

        if (StringUtils.isEmpty(getRepoName())) {
            return false;
        }

        // 不共享但是没有本机的workroot
        if (!isShared() && StringUtils.isEmpty(getWorkRoot())) {
            return false;
        }

        // 共享但是没有workroot配置
        if (isShared() && workroots.get().isEmpty()) {
            return false;
        }

        return true;
    }


    /**
     * 是否适合本机安装
     *
     * @return
     */
    public boolean suitable() {
        if (ResourceModuleContext.getFactory(getIdentity()) == null) {
            return false;
        }
        return validate();
    }

    @Override
    public final void update(String srcRepo) {
        InstalledComponent c = ResourceModuleContext.getInstalledComponent(srcRepo);
        this.setWorkRoot(c.getWorkRoot());
        this.setShared(c.isShared());
        if (getConfig() != null) {
            this.getConfig().update(srcRepo);
        }
    }

    /**
     * 仅生成安装组件(不安装)
     *
     * @return 仓库安装组件
     */
    public final InstalledComponent<T> generateComponent() {
        RepositoryFactoryProvider<T> fac = ResourceModuleContext.getFactory(getIdentity());
        return new InstalledComponent<T>(fac, getRepoName(), getWorkRoot(), isShared());
    }

}

四、支持版本

产品线

版本

支持情况

备注

FR10.0支持

五、插件注册

plugin.xml
<extra-core>
        <AlternateResultProvider class="your class name"/>
</extra-core>

六、原理说明

如DmlController和SynchronizedLiveDataModelUtils所示,当共享数据集调用缓存数据时会先从插件中查找,找不到再到产品的缓存中查找;填报缓存失效时,会调用对于的插件扩展缓存插件移除对于的缓存。

调用均通过Set<AlternateResultProvider> set = ExtraClassManager.getInstance().getArray(AlternateResultProvider.XML_TAG);获取到所有插件中申明的自定义缓存策略接口实例。

七、特殊限制说明

用于共享数据集缓存策略扩展场景时,开发者需要使用其他接口对数据进行缓存。因为标准产品中只调用了读缓存,没有直接提供写缓存的调用,需要借助其他插件接口来生成并写入缓存。以及监控缓存的生命周期。

当开发者开发了一个插件,想对外提供一个自身插件数据的访问或者注册的入口,也可以通过该接口实现(需要一定的设计能力),从实践中看,这也是该接口的一个主要用途。

八、常用链接

demo地址:demo-alternate-result-provider

九、开源案例

免责声明:所有文档中的开源示例,均为开发者自行开发并提供。仅用于参考和学习使用,开发者和官方均无义务对开源案例所涉及的所有成果进行教学和指导。若作为商用一切后果责任由使用者自行承担。

  • No labels