diff --git a/src/main/java/com/fanruan/api/cal/CalculatorKit.java b/src/main/java/com/fanruan/api/cal/CalculatorKit.java new file mode 100644 index 0000000..fead792 --- /dev/null +++ b/src/main/java/com/fanruan/api/cal/CalculatorKit.java @@ -0,0 +1,88 @@ +package com.fanruan.api.cal; + +import com.fanruan.api.cal.namespace.SheetInterval4CheckNameSpace; +import com.fanruan.api.session.SessionKit; +import com.fr.base.ParameterMapNameSpace; +import com.fr.base.TableDataNameSpace; +import com.fr.data.TableDataSource; +import com.fr.report.report.Report; +import com.fr.script.Calculator; +import com.fr.stable.StringUtils; +import com.fr.stable.script.CalculatorProvider; +import com.fr.stable.script.NameSpace; +import com.fr.stable.web.SessionProvider; +import com.fr.third.javax.annotation.Nullable; +import com.fr.web.core.ReportSessionIDInfor; +import com.fr.web.core.TemplateSessionIDInfo; +import com.fr.web.session.SessionIDInfo; + +import java.util.Map; + +/** + * @ClassName CalculatorKit + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + * 帆软算子工具类(主要用于公式计算) + */ +public class CalculatorKit { + /** + * 创建一个基础算子(基础算子仅支持函数计算.比如SUM()函数) + * + * @return 算子 + */ + public static CalculatorProvider createCalculator() { + return Calculator.createCalculator(); + } + + /** + * 创建一个带参数计算的算子 + * (可以支持计算入参paraMap里面相关的带参函数, + * 比如paraMap带有参数a->1,b->2,CalculatorKit.createCalculator(paraMap).evalValue("SUM(a,b)")输出3) + * + * @return 算子 + */ + public static CalculatorProvider createCalculator(Map paraMap) { + ParameterMapNameSpace nameSpace = ParameterMapNameSpace.create(paraMap); + CalculatorProvider calculator = createCalculator(); + calculator.pushNameSpace(nameSpace); + return calculator; + } + + /** + * 创建一个支持单元格以及模板数据集计算的算子 + * + * @param sessionID 模板的sessionID + * @param paraMap 其他参数 + * @return + */ + public static CalculatorProvider createCalculator(@Nullable String sessionID, @Nullable Map paraMap) { + ParameterMapNameSpace nameSpace = ParameterMapNameSpace.create(paraMap); + CalculatorProvider calculator = createCalculator(); + calculator.pushNameSpace(nameSpace); + if (StringUtils.isNotEmpty(sessionID)) { + SessionProvider rsii = SessionKit.getSession(sessionID); + if (rsii != null) { + if (rsii instanceof TemplateSessionIDInfo) { + //支持模板单元格值计算 + calculator.setAttribute(TableDataSource.KEY, ((TemplateSessionIDInfo) rsii).getTableDataSource()); + calculator.pushNameSpace(SessionIDInfo.asNameSpace(sessionID)); + if (rsii instanceof ReportSessionIDInfor) { + //支持跨sheet计算 + calculator.pushNameSpace(SheetInterval4CheckNameSpace.getInstance()); + calculator.setAttribute(Report.KEY, ((ReportSessionIDInfor) rsii).getReport2Show(0)); + } + } + } + } + return calculator; + } + + /** + * 返回服务器数据集的算子空间(可以通过调用calculator.pushNameSpace()将算子空间塞进算子实例,从而支持服务器数据集相关的函数计算) + * @return + */ + public static NameSpace getServerTableDataNameSpace(){ + return TableDataNameSpace.getInstance(); + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/cal/FormulaKit.java b/src/main/java/com/fanruan/api/cal/FormulaKit.java index 2cb45fe..c32acc4 100644 --- a/src/main/java/com/fanruan/api/cal/FormulaKit.java +++ b/src/main/java/com/fanruan/api/cal/FormulaKit.java @@ -2,23 +2,31 @@ package com.fanruan.api.cal; import com.fanruan.api.err.KitError; import com.fr.base.BaseFormula; +import com.fr.log.FineLoggerFactory; +import com.fr.parser.FRLexer; +import com.fr.parser.FRParser; import com.fr.script.Calculator; import com.fr.stable.FormulaProvider; +import com.fr.stable.StringUtils; import com.fr.stable.UtilEvalError; import com.fr.stable.script.CalculatorProvider; +import com.fr.stable.script.Expression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.StringReader; + /** * @author richie * @version 10.0 - * Created by richie on 2019-08-15 - * 公式计算相关的工具类 + * Created by richie on 2019-08-15 + * 公式计算相关的工具类 */ public class FormulaKit { /** * 计算公式的值,会新建一个算子对象来计算该公式 + * * @param formula 公式内容 * @return 公式计算后的结果值 * @throws KitError 如果计算过程中出现错误,则抛出此异常 @@ -29,8 +37,9 @@ public class FormulaKit { /** * 计算公式的值 + * * @param calculator 自定义算子 - * @param formula 公式内容 + * @param formula 公式内容 * @return 公式计算后的结果值 * @throws KitError 如果计算过程中出现错误,则抛出此异常 */ @@ -44,10 +53,50 @@ public class FormulaKit { /** * 生成公式对象 + * * @param content 公式的内容 * @return 公式对象 */ public static @NotNull FormulaProvider newFormula(Object content) { return BaseFormula.createFormulaBuilder().build(content); } + + /** + * 检查公式内容合法性 + * + * @param content 公式文本(公式的开头等号要先去掉,如果想校验等号开头的内容需要传Formula对象) + * @return 如果非空且不合法返回false, 反之返回true + */ + public static boolean checkFormulaContent(String content) { + String formulaText = content.trim(); + + if (StringUtils.isNotEmpty(formulaText)) { + StringReader in = new StringReader(formulaText); + FRLexer lexer = new FRLexer(in); + FRParser parser = new FRParser(lexer); + + Expression expression = null; + try { + expression = parser.parse(); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + + return null != expression; + } + return true; + } + + /** + * 公式合法性校验 + * + * @param formula 公式对象 + * @return 当前公式是否合法 + */ + public static boolean checkFormulaContent(FormulaProvider formula) { + if (formula == null) { + return true; + } + return checkFormulaContent(formula.getPureContent()); + } } diff --git a/src/main/java/com/fanruan/api/cal/namespace/SheetInterval4CheckNameSpace.java b/src/main/java/com/fanruan/api/cal/namespace/SheetInterval4CheckNameSpace.java new file mode 100644 index 0000000..f31bed8 --- /dev/null +++ b/src/main/java/com/fanruan/api/cal/namespace/SheetInterval4CheckNameSpace.java @@ -0,0 +1,67 @@ +package com.fanruan.api.cal.namespace; + +import com.fr.general.ComparatorUtils; +import com.fr.log.FineLoggerFactory; +import com.fr.main.FineBook; +import com.fr.parser.SheetIntervalLiteral; +import com.fr.report.report.Report; +import com.fr.report.report.WriteECReport; +import com.fr.stable.StringUtils; +import com.fr.stable.script.AbstractNameSpace; +import com.fr.stable.script.CalculatorProvider; +import com.fr.stable.script.ColumnRowRange; + +/** + * @deprecated 当sdk因为其他需求有主体jar要求的时候这边可以删掉 + * 主体代码里面的这个类被混淆了,sdk抄一份一样的 + * @author Carl + * 填报的时候处理跨域的值 + */ + +public class SheetInterval4CheckNameSpace extends AbstractNameSpace { + public static final SheetInterval4CheckNameSpace SC = new SheetInterval4CheckNameSpace(); + + private SheetInterval4CheckNameSpace(){} + + public static SheetInterval4CheckNameSpace getInstance() { + return SC; + } + + @Override + public Object getVariable(Object var, CalculatorProvider calculator) { + // TODO carl:真是乱 + if (!(var instanceof SheetIntervalLiteral)) { + return null; + } + + Report report = calculator.getAttribute(Report.KEY); + if (report == null) { + return null; + } + + FineBook wb = report.getBook(); + SheetIntervalLiteral sil = (SheetIntervalLiteral)var; + if (wb == null || StringUtils.isBlank(sil.getSheetName())) { + return null; + } + + for (int i = 0; i < wb.getReportCount(); i++) { + if (ComparatorUtils.equals(wb.getReportName(i), sil.getSheetName())) { + Report rp = wb.getReport(i); + if (rp instanceof WriteECReport) { + if (sil.getSheetAtom() instanceof ColumnRowRange) { + return ((WriteECReport)rp).resolveColumnRowRange((ColumnRowRange)sil.getSheetAtom(), null); + } else { + // TODO 暂时只有 ColumnRowRange和Ambiguity两种,考虑到以后会加就弄到外面来处理 + // Ambiguity的情况不处理 + FineLoggerFactory.getLogger().error("UnResolvedVariable " + sil.toString()); + } + } + break; + } + } + + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/env/EnvKit.java b/src/main/java/com/fanruan/api/env/EnvKit.java index bf24b3e..fc59660 100644 --- a/src/main/java/com/fanruan/api/env/EnvKit.java +++ b/src/main/java/com/fanruan/api/env/EnvKit.java @@ -1,6 +1,22 @@ package com.fanruan.api.env; +import com.fanruan.api.env.shell.ModuleShell; +import com.fr.base.operator.common.CommonOperator; +import com.fr.chart.activator.ChartBaseActivator; +import com.fr.cluster.engine.activator.standalone.StandaloneModeActivator; +import com.fr.config.activator.BaseDBActivator; +import com.fr.config.activator.ConfigurationActivator; +import com.fr.env.operator.CommonOperatorImpl; +import com.fr.general.I18nResource; +import com.fr.module.Module; +import com.fr.module.tool.ActivatorToolBox; +import com.fr.report.ReportActivator; +import com.fr.report.RestrictionActivator; +import com.fr.report.module.ReportBaseActivator; +import com.fr.scheduler.SchedulerActivator; +import com.fr.stable.StringUtils; import com.fr.stable.project.ProjectConstants; +import com.fr.store.StateServerActivator; import com.fr.workspace.WorkContext; import com.fr.workspace.Workspace; import com.fr.workspace.resource.WorkResource; @@ -42,6 +58,32 @@ public class EnvKit { } } + /** + * 创建本地服务器启动模块 + * + * @param envPath 本地工程路径 : D:\\FineReport_10.0\\webapps\\webroot\\WEB-INF + * @return 模块代理对象 使用ModuleShell的start和stop控制模块启停 + */ + public static ModuleShell createLocalStartModule(String envPath) { + Module module = ActivatorToolBox.simpleLink( + new BaseDBActivator(), + new ConfigurationActivator(), + new StandaloneModeActivator(), + new StateServerActivator(), + new SchedulerActivator(), + new ReportBaseActivator(), + new RestrictionActivator(), + new ReportActivator(), + new ChartBaseActivator() + ); + SimpleWork.supply(CommonOperator.class, new CommonOperatorImpl()); + if (StringUtils.isNotEmpty(envPath)) { + SimpleWork.checkIn(envPath); + } + I18nResource.getInstance(); + return new ModuleShell(module); + } + /** * 退出当前工作目录 */ diff --git a/src/main/java/com/fanruan/api/env/shell/ModuleShell.java b/src/main/java/com/fanruan/api/env/shell/ModuleShell.java new file mode 100644 index 0000000..9df9f5b --- /dev/null +++ b/src/main/java/com/fanruan/api/env/shell/ModuleShell.java @@ -0,0 +1,33 @@ +package com.fanruan.api.env.shell; + +import com.fr.module.Module; + +/** + * 模块封装 + */ +public class ModuleShell { + private Module module; + + public ModuleShell(Module module) { + this.module = module; + } + + /** + * 启动模块 + */ + public void start() { + if (module != null) { + module.start(); + } + } + + /** + * 关闭模块 + */ + public void stop() { + if (module != null) { + module.stop(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/function/FunctionKit.java b/src/main/java/com/fanruan/api/function/FunctionKit.java new file mode 100644 index 0000000..c44ce3f --- /dev/null +++ b/src/main/java/com/fanruan/api/function/FunctionKit.java @@ -0,0 +1,59 @@ +package com.fanruan.api.function; + +import com.fanruan.api.function.shell.FineFunc; +import com.fr.file.FunctionManager; +import com.fr.script.Calculator; +import com.fr.stable.StringUtils; +import com.fr.stable.script.FunctionDef; + +import java.util.ArrayList; +import java.util.List; + +/** + * @ClassName FunctionKit + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + * 函数相关工具类 + */ +public class FunctionKit { + /** + * 新增一个自定义函数 + * + * @param fineFunc 自定义函数对象 + */ + public static void addFunc(FineFunc fineFunc) { + if (fineFunc == null) { + return; + } + if (Calculator.createCalculator().resolveMethod(fineFunc.getName()) == null) { + FunctionManager.getInstance().addFunctionDef(new FunctionDef(fineFunc.getName(), fineFunc.getDescription(), fineFunc.getClassName())); + } + } + + /** + * 根据自定义函数类路径移除一个自定义函数 + * + * @param className 自定义函数类路径 + */ + public static void removeFunc(String className) { + if (StringUtils.isEmpty(className)) { + return; + } + int count = FunctionManager.getInstance().getFunctionDefCount(); + List functionDefList = new ArrayList(count); + for (int i = 0; i < count; i++) { + FunctionDef functionDef = FunctionManager.getInstance().getFunctionDef(i); + if (!className.equalsIgnoreCase(functionDef.getClassName())) { + functionDefList.add(functionDef); + } + } + if (functionDefList.size() == count) { + return; + } + FunctionManager.getInstance().clearAllFunctionDef(); + for (FunctionDef fun : functionDefList) { + FunctionManager.getInstance().addFunctionDef(fun); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/function/shell/FineFunc.java b/src/main/java/com/fanruan/api/function/shell/FineFunc.java new file mode 100644 index 0000000..c66e17a --- /dev/null +++ b/src/main/java/com/fanruan/api/function/shell/FineFunc.java @@ -0,0 +1,46 @@ +package com.fanruan.api.function.shell; + +import com.fr.stable.StringUtils; + +/** + * 自定义函数封装 + */ +public class FineFunc { + private String name;//函数名 + private String description;//描述 + private String className;//完整类名 + + public FineFunc(String name, String description, String className) { + this.name = name; + this.description = description; + this.className = className; + } + + public FineFunc(String name, String className) { + this(name, StringUtils.EMPTY, className); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/report/TemplateKit.java b/src/main/java/com/fanruan/api/report/TemplateKit.java new file mode 100644 index 0000000..af520eb --- /dev/null +++ b/src/main/java/com/fanruan/api/report/TemplateKit.java @@ -0,0 +1,24 @@ +package com.fanruan.api.report; + +import com.fr.io.TemplateWorkBookIO; +import com.fr.main.TemplateWorkBook; +/** + * @ClassName TemplateKit + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + * 模板操作相关工具类 + */ +public class TemplateKit { + /** + * 通过相对路径读取模板 + * + * @param relativePath 相对路径(相对reportlets目录) + * @return 模板对象 + * @throws Exception 模板读取异常 + */ + public static TemplateWorkBook readTemplateByPath(String relativePath) throws Exception { + return TemplateWorkBookIO.readTemplateWorkBook( + relativePath); + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/report/analy/AnalyKit.java b/src/main/java/com/fanruan/api/report/analy/AnalyKit.java new file mode 100644 index 0000000..e1f4da8 --- /dev/null +++ b/src/main/java/com/fanruan/api/report/analy/AnalyKit.java @@ -0,0 +1,47 @@ +package com.fanruan.api.report.analy; + +import com.fanruan.api.report.analy.data.TreeNode; +import com.fr.main.workbook.ResultWorkBook; +import com.fr.report.report.Report; +import com.fr.report.report.ResultReport; +import com.fr.report.worksheet.AnalysisRWorkSheet; +import com.fr.script.Calculator; +import com.fr.web.core.TreeHTMLWriter; +import com.fr.web.core.reportcase.WebElementReportCase; +import com.fr.web.output.html.chwriter.ViewCellWriter; +import com.fr.web.request.EmptyRepository; + +import java.util.Map; +/** + * @ClassName AnalyKit + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + * 数据分析相关工具类 + */ +public class AnalyKit { + /** + * 折叠树模板,将结果报表行转成一个树形数据结构 + * + * @param book 结果报表 + * @param index sheet索引 + * @return 树节点集合 + */ + public static Map generateResultBookTree(ResultWorkBook book, int index) { + ResultReport resultWS = book.getResultReport(index); + if (!(resultWS instanceof AnalysisRWorkSheet)) { + //只有分析预览支持折叠树 + throw new UnsupportedOperationException(); + } + AnalysisRWorkSheet analysisRWorkSheet = (AnalysisRWorkSheet) resultWS; + + Calculator c = Calculator.createCalculator(); + c.setAttribute(Report.KEY, analysisRWorkSheet); + TreeHTMLWriter htmlWriter = new TreeHTMLWriter(); + ViewCellWriter cellHtmlWriter = new ViewCellWriter(new EmptyRepository(), 1, resultWS.getReportSettings(), true); + htmlWriter.writeReportToHtml(new WebElementReportCase(analysisRWorkSheet, new EmptyRepository()), 1, cellHtmlWriter, new EmptyRepository(), ""); + cellHtmlWriter.dealWithAllTreeNodeRelation(c); + + return AnalyKitHelper.generateNodeTree(analysisRWorkSheet); + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/report/analy/AnalyKitHelper.java b/src/main/java/com/fanruan/api/report/analy/AnalyKitHelper.java new file mode 100644 index 0000000..96f0f07 --- /dev/null +++ b/src/main/java/com/fanruan/api/report/analy/AnalyKitHelper.java @@ -0,0 +1,75 @@ +package com.fanruan.api.report.analy; + +import com.fanruan.api.report.analy.data.TreeNode; +import com.fr.cache.list.IntList; +import com.fr.form.ui.Widget; +import com.fr.report.cell.ResultCellElement; +import com.fr.report.cell.WidgetAttrElem; +import com.fr.report.web.button.form.TreeNodeToggleButton; +import com.fr.report.worksheet.AnalysisRWorkSheet; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * 数据分析相关的业务类,此类是为了接盘AnalyKit中的庞杂的私有方法,增强AnalyKit的可读性 + */ +public class AnalyKitHelper { + + public static Map generateNodeTree(AnalysisRWorkSheet resultWS) { + int rowSize = resultWS.getRowCount(); + Map nodeMap = new HashMap(); + + for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {//遍历行 + ResultCellElement treeNodeCell = findToggleCell(resultWS, rowIndex); + if (treeNodeCell != null) { + Widget widget = ((WidgetAttrElem) treeNodeCell).getWidget(); + IntList sonList = ((TreeNodeToggleButton) widget).getRelativeIndexList(); + if (sonList != null && sonList.size() > 1) { + if (sonList.get(0) == -1) { + //折叠行 + if (nodeMap.containsKey(treeNodeCell.getRow())) { + continue; + } + buildNodeMap(resultWS, treeNodeCell, nodeMap, -1); + } else { + //折叠列 暂不处理 + } + } + } + } + return nodeMap; + } + + private static ResultCellElement findToggleCell(AnalysisRWorkSheet reportCase, int rowIndex) { + Iterator cellIterator = reportCase.getRow(rowIndex); + while (cellIterator.hasNext()) { + ResultCellElement tmpCell = (ResultCellElement) cellIterator.next(); + if (tmpCell instanceof WidgetAttrElem) { + if (((WidgetAttrElem) tmpCell).getWidget() instanceof TreeNodeToggleButton) { + return tmpCell; + } + } + } + return null; + } + + private static void buildNodeMap(AnalysisRWorkSheet reportCase, ResultCellElement cellElment, Map nodeMap, int parent) { + if (cellElment == null) { + return; + } + TreeNodeToggleButton toggleButton = (TreeNodeToggleButton) ((WidgetAttrElem) cellElment).getWidget(); + int self = cellElment.getRow(); + IntList sonList = toggleButton.getRelativeIndexList(); + if (sonList != null && sonList.size() > 1) { + if (sonList.get(0) == -1) {//折叠行 + nodeMap.put(self, new TreeNode(self, parent, sonList)); + int size = sonList.size(); + for (int i = 0; i < size; i++) { + buildNodeMap(reportCase, findToggleCell(reportCase, sonList.get(i)), nodeMap, self); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/report/analy/data/TreeNode.java b/src/main/java/com/fanruan/api/report/analy/data/TreeNode.java new file mode 100644 index 0000000..46f9bab --- /dev/null +++ b/src/main/java/com/fanruan/api/report/analy/data/TreeNode.java @@ -0,0 +1,52 @@ +package com.fanruan.api.report.analy.data; + +import com.fr.cache.list.IntList; + +/** + * 折叠行树节点对象 + */ +public class TreeNode { + private int self;//自身所在行 + private int parent;//父节点所在行 + private IntList sons;//子节点 + + public TreeNode(int self, int parent, IntList sons) { + this.self = self; + this.parent = parent; + this.sons = sons; + } + + public void setParent(int parent) { + this.parent = parent; + } + + public void setSons(IntList sons) { + this.sons = sons; + } + + public void setSelf(int self) { + this.self = self; + } + + public int getParent() { + return parent; + } + + public int getSelf() { + return self; + } + + public IntList getSons() { + return sons; + } + + @Override + public String toString() { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(self).append("#").append(parent); + if (sons != null) { + stringBuffer.append("#").append(sons.toString()); + } + return stringBuffer.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/util/TransmissionKit.java b/src/main/java/com/fanruan/api/util/TransmissionKit.java new file mode 100644 index 0000000..f145198 --- /dev/null +++ b/src/main/java/com/fanruan/api/util/TransmissionKit.java @@ -0,0 +1,58 @@ +package com.fanruan.api.util; + +import com.fanruan.api.util.shell.BaseSmsBody; +import com.fanruan.api.util.shell.EmailBody; +import com.fr.base.EmailManager; +import com.fr.base.sms.SMSManager; + +/** + * @ClassName TransmissionKit + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + * fine kit for data transmission.(eg. email or sms...) + */ +public class TransmissionKit { + /** + * 服务端是否支持短信服务 + * + * @return 支持返回true 否则false + */ + public static boolean isSmsFuncSupport() { + return SMSManager.getInstance().isSMSFuncSupport(); + } + + /** + * 发送短信 + * + * @param baseSmsBody 短信实体 + * @return 发送成功返回true 否则false + * @throws Exception + */ + public static boolean sendSms(BaseSmsBody baseSmsBody) throws Exception { + return baseSmsBody.send(); + } + + /** + * 发送邮件 + * + * @param emailBody 邮件实体 + * @return 发送成功返回true否则false + * @throws Exception + */ + public static boolean sendEmail(EmailBody emailBody) throws Exception { + EmailManager.getInstance().send( + emailBody.getToAddress(), + emailBody.getCcAddress(), + emailBody.getBccAddress(), + emailBody.getFromAddress(), + emailBody.getSubject(), + emailBody.getBodyContent(), + emailBody.getAttaches(), + emailBody.getFormat(), + emailBody.getContentAttaches(), + emailBody.getSessionId() + ); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/fanruan/api/util/shell/BaseSmsBody.java b/src/main/java/com/fanruan/api/util/shell/BaseSmsBody.java new file mode 100644 index 0000000..863abb4 --- /dev/null +++ b/src/main/java/com/fanruan/api/util/shell/BaseSmsBody.java @@ -0,0 +1,27 @@ +package com.fanruan.api.util.shell; + +/** + * 抽象短信体 + * + * @author vito + * @date 2019-07-24 + */ +public abstract class BaseSmsBody { + private String templateCode; + + public String getTemplateCode() { + return templateCode; + } + + void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + /** + * 发送 + * + * @return 发送结果 + * @throws Exception 发送异常 + */ + public abstract boolean send() throws Exception; +} diff --git a/src/main/java/com/fanruan/api/util/shell/BatchSmsBody.java b/src/main/java/com/fanruan/api/util/shell/BatchSmsBody.java new file mode 100644 index 0000000..8c3437a --- /dev/null +++ b/src/main/java/com/fanruan/api/util/shell/BatchSmsBody.java @@ -0,0 +1,94 @@ +package com.fanruan.api.util.shell; + +import com.fr.base.sms.SMSManager; +import com.fr.json.JSONArray; + +import java.util.List; + +/** + * 批量短信体 + * + * @author vito + * @date 2019-07-24 + */ +public class BatchSmsBody extends BaseSmsBody { + private List mobiles; + private List receivers; + private JSONArray para; + + private BatchSmsBody() { + } + + public List getMobiles() { + return mobiles; + } + + public List getReceivers() { + return receivers; + } + + public JSONArray getPara() { + return para; + } + + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public boolean send() throws Exception { + return SMSManager.getInstance().batchSendSMS( + getTemplateCode(), + getMobiles(), + getPara(), + getReceivers()); + } + + public static final class Builder { + private String templateCode; + private List mobiles; + private List receivers; + private JSONArray para; + + private Builder() { + } + + public Builder templateCode(String templateCode) { + this.templateCode = templateCode; + return this; + } + + public Builder mobiles(List mobiles) { + this.mobiles = mobiles; + return this; + } + + public Builder receivers(List receivers) { + this.receivers = receivers; + return this; + } + + public Builder para(JSONArray para) { + this.para = para; + return this; + } + + public BatchSmsBody build() { + if (templateCode == null) { + throw new IllegalArgumentException("templateCode should not be null"); + } + if (mobiles == null) { + throw new IllegalArgumentException("mobiles should not be null"); + } + if (para == null) { + throw new IllegalArgumentException("para should not be null"); + } + BatchSmsBody batchSmsBody = new BatchSmsBody(); + batchSmsBody.setTemplateCode(templateCode); + batchSmsBody.receivers = this.receivers; + batchSmsBody.mobiles = this.mobiles; + batchSmsBody.para = this.para; + return batchSmsBody; + } + } +} diff --git a/src/main/java/com/fanruan/api/util/shell/EmailBody.java b/src/main/java/com/fanruan/api/util/shell/EmailBody.java new file mode 100644 index 0000000..dea730c --- /dev/null +++ b/src/main/java/com/fanruan/api/util/shell/EmailBody.java @@ -0,0 +1,219 @@ +package com.fanruan.api.util.shell; + +import com.fr.base.EmailAttachment; +import org.jetbrains.annotations.Nullable; + +/** + * 邮件体 + * + * @author vito + * @date 2019-07-24 + */ +public class EmailBody { + private String toAddress; + private String ccAddress; + private String bccAddress; + private String fromAddress; + private String subject; + private String bodyContent; + private EmailAttachment[] attaches; + private String format; + private EmailAttachment[] contentAttaches; + private String sessionId; + + private EmailBody(Builder builder) { + this.toAddress = builder.toAddress; + this.ccAddress = builder.ccAddress; + this.bccAddress = builder.bccAddress; + this.fromAddress = builder.fromAddress; + this.subject = builder.subject; + this.bodyContent = builder.bodyContent; + this.attaches = builder.attaches; + this.format = builder.format; + this.contentAttaches = builder.contentAttaches; + this.sessionId = builder.sessionId; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public String getToAddress() { + return toAddress; + } + + public String getCcAddress() { + return ccAddress; + } + + public String getBccAddress() { + return bccAddress; + } + + public String getFromAddress() { + return fromAddress; + } + + public String getSubject() { + return subject; + } + + public String getBodyContent() { + return bodyContent; + } + + public EmailAttachment[] getAttaches() { + return attaches; + } + + public String getFormat() { + return format; + } + + public EmailAttachment[] getContentAttaches() { + return contentAttaches; + } + + public String getSessionId() { + return sessionId; + } + + public static final class Builder { + private String toAddress; + private String ccAddress; + private String bccAddress; + private String fromAddress; + private String subject; + private String bodyContent; + private EmailAttachment[] attaches; + private String format; + private EmailAttachment[] contentAttaches; + private String sessionId; + + private Builder() { + } + + public EmailBody build() { + if (toAddress == null) { + throw new IllegalArgumentException("toAddress should not be null"); + } + if (subject == null) { + throw new IllegalArgumentException("subject should not be null"); + } + if (bodyContent == null) { + throw new IllegalArgumentException("bodyContent should not be null"); + } + return new EmailBody(this); + } + + /** + * 设置收件人地址 + * + * @param toAddress 收件人地址 + * @return 建造者 + */ + public Builder toAddress(String toAddress) { + this.toAddress = toAddress; + return this; + } + + /** + * 设置抄送地址 + * + * @param ccAddress 抄送地址 + * @return 建造者 + */ + public Builder ccAddress(@Nullable String ccAddress) { + this.ccAddress = ccAddress; + return this; + } + + /** + * 设置密送地址 + * + * @param bccAddress 密送地址 + * @return 建造者 + */ + public Builder bccAddress(@Nullable String bccAddress) { + this.bccAddress = bccAddress; + return this; + } + + /** + * 设置发件人地址 + * + * @param fromAddress 发件人地址 + * @return 建造者 + */ + public Builder fromAddress(@Nullable String fromAddress) { + this.fromAddress = fromAddress; + return this; + } + + /** + * 设置主题 + * + * @param subject 主题 + * @return 建造者 + */ + public Builder subject(String subject) { + this.subject = subject; + return this; + } + + /** + * 设置正文 + * + * @param bodyContent 正文 + * @return 建造者 + */ + public Builder bodyContent(String bodyContent) { + this.bodyContent = bodyContent; + return this; + } + + /** + * 设置附件 + * + * @param attaches 附件 + * @return 建造者 + */ + public Builder attaches(@Nullable EmailAttachment[] attaches) { + this.attaches = attaches; + return this; + } + + /** + * 设置格式 + * + * @param format 格式 + * @return 建造者 + */ + public Builder format(@Nullable String format) { + this.format = format; + return this; + } + + /** + * 设置邮件正文显示的附件 + * + * @param contentAttaches 邮件正文显示的附件 + * @return 建造者 + */ + public Builder contentAttaches(@Nullable EmailAttachment[] contentAttaches) { + this.contentAttaches = contentAttaches; + return this; + } + + /** + * 设置报表的session id + * + * @param sessionId 报表的session id + * @return 建造者 + */ + public Builder sessionId(@Nullable String sessionId) { + this.sessionId = sessionId; + return this; + } + } +} diff --git a/src/main/java/com/fanruan/api/util/shell/SingleSmsBody.java b/src/main/java/com/fanruan/api/util/shell/SingleSmsBody.java new file mode 100644 index 0000000..eae5aec --- /dev/null +++ b/src/main/java/com/fanruan/api/util/shell/SingleSmsBody.java @@ -0,0 +1,162 @@ +package com.fanruan.api.util.shell; + +import com.fr.base.sms.SMSManager; +import com.fr.json.JSONObject; +import org.jetbrains.annotations.Nullable; + +/** + * 单个短信体 + * + * @author vito + * @date 2019-07-24 + */ +public class SingleSmsBody extends BaseSmsBody { + private String mobile; + private JSONObject para; + private String receiver; + private boolean needRecord; + + private SingleSmsBody() { + } + + /** + * 获取收集号码 + * + * @return 手机号码 + */ + public String getMobile() { + return mobile; + } + + /** + * 获取参数 + * + * @return 模板参数 + */ + public JSONObject getPara() { + return para; + } + + /** + * 获取接收者 + * + * @return 接收者 + */ + @Nullable + public String getReceiver() { + return receiver; + } + + /** + * 是否需要记录 + * + * @return 是否需要记录 + */ + public boolean isNeedRecord() { + return needRecord; + } + + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public boolean send() throws Exception { + return SMSManager.getInstance().sendSMS( + getTemplateCode(), + getMobile(), + getPara(), + getReceiver(), + isNeedRecord()); + } + + public static final class Builder { + private String templateCode; + private String mobile; + private JSONObject para; + private String receiver; + private boolean needRecord; + + private Builder() { + } + + /** + * 设置模板代码 + * + * @param templateCode 模板代码 + * @return builder + */ + public Builder templateCode(String templateCode) { + this.templateCode = templateCode; + return this; + } + + /** + * 设置手机号码 + * + * @param mobile 手机号码 + * @return builder + */ + public Builder mobile(String mobile) { + this.mobile = mobile; + return this; + } + + /** + * 设置模板参数 + * + * @param para 模板参数 + * @return builder + */ + public Builder para(JSONObject para) { + this.para = para; + return this; + } + + /** + * 设置接受对象,可不传 + * + * @param receiver 接受对象 + * @return builder + */ + public Builder receiver(@Nullable String receiver) { + this.receiver = receiver; + return this; + } + + /** + * 设置是否需要记录 + * + * @param needRecord 需要记 + * @return builder + */ + public Builder needRecord(boolean needRecord) { + this.needRecord = needRecord; + return this; + } + + /** + * 构建 + * + * @return 单个短信体 + */ + public SingleSmsBody build() { + if (templateCode == null) { + throw new IllegalArgumentException("templateCode should not be null"); + } + if (mobile == null) { + throw new IllegalArgumentException("mobile should not be null"); + } + if (para == null) { + throw new IllegalArgumentException("para should not be null"); + } + SingleSmsBody singleSmsBody = new SingleSmsBody(); + singleSmsBody.setTemplateCode(templateCode); + singleSmsBody.para = this.para; + singleSmsBody.receiver = this.receiver; + singleSmsBody.needRecord = this.needRecord; + singleSmsBody.mobile = this.mobile; + return singleSmsBody; + } + } +} diff --git a/src/test/java/com/fanruan/api/Prepare.java b/src/test/java/com/fanruan/api/Prepare.java index d74dfbf..d93fd1f 100644 --- a/src/test/java/com/fanruan/api/Prepare.java +++ b/src/test/java/com/fanruan/api/Prepare.java @@ -1,44 +1,29 @@ package com.fanruan.api; -import com.fr.config.dao.DaoContext; -import com.fr.config.dao.impl.LocalClassHelperDao; -import com.fr.config.dao.impl.LocalEntityDao; -import com.fr.config.dao.impl.LocalXmlEntityDao; -import com.fr.runtime.FineRuntime; -import com.fr.store.StateHubManager; -import com.fr.store.impl.MemoryLock; -import com.fr.store.impl.MemoryStore; -import com.fr.transaction.Configurations; -import com.fr.transaction.LocalConfigurationHelper; +import com.fanruan.api.env.EnvKit; +import com.fanruan.api.env.shell.ModuleShell; import org.junit.After; import org.junit.Before; /** * @author richie * @version 10.0 - * Created by richie on 2019-08-09 + * Created by richie on 2019-08-09 */ public class Prepare { + private ModuleShell module; @Before public void start() { - FineRuntime.start(); - DaoContext.setEntityDao(new LocalEntityDao()); - DaoContext.setClassHelperDao(new LocalClassHelperDao()); - DaoContext.setXmlEntityDao(new LocalXmlEntityDao()); - Configurations.setHelper(new LocalConfigurationHelper()); - StateHubManager.setStorage(new MemoryStore()); - StateHubManager.setLock(new MemoryLock()); + module = EnvKit.createLocalStartModule(""); + module.start(); } @After public void stop() { - DaoContext.setEntityDao(null); - DaoContext.setClassHelperDao(null); - DaoContext.setXmlEntityDao(null); - Configurations.setHelper(null); - StateHubManager.setStorage(null); - StateHubManager.setLock(null); + if (module != null) { + module.stop(); + } } } diff --git a/src/test/java/com/fanruan/api/cal/CalculatorKitEnvTest.java b/src/test/java/com/fanruan/api/cal/CalculatorKitEnvTest.java new file mode 100644 index 0000000..0b56fd2 --- /dev/null +++ b/src/test/java/com/fanruan/api/cal/CalculatorKitEnvTest.java @@ -0,0 +1,49 @@ +package com.fanruan.api.cal; + +import com.fanruan.api.Prepare; +import com.fr.invoke.Reflect; +import com.fr.log.FineLoggerFactory; +import com.fr.main.impl.WorkBook; +import com.fr.main.workbook.ResultWorkBook; +import com.fr.report.cell.CellElementValueConverterRegistry; +import com.fr.stable.ActorConstants; +import com.fr.stable.ActorFactory; +import com.fr.web.core.ReportSessionIDInfor; +import com.fr.web.core.SessionPoolManager; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * 需要启动模块的算子单元测试 + * + * @ClassName CalculatorKitEnvTest + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + */ +public class CalculatorKitEnvTest extends Prepare { + @Test + public void testSheetInterval() { + WorkBook tpl = new WorkBook(); + Map paraMap = new HashMap(0); + String path = "/com/fanruan/api/report/sheets.cpt"; + try { + tpl.readStream(CalculatorKitEnvTest.class.getResourceAsStream(path)); + ResultWorkBook resultWorkBook = tpl.execute(paraMap, ActorFactory.getActor(ActorConstants.TYPE_WRITE)); + ReportSessionIDInfor sessionIDInfor = new ReportSessionIDInfor(paraMap, path, ActorFactory.getActor(ActorConstants.TYPE_WRITE)).buildResultWorkBook(resultWorkBook); + String sessionId = Reflect.on(SessionPoolManager.class).call("addSessionIDInfor", sessionIDInfor).get(); + CellElementValueConverterRegistry.complete(); + Assert.assertEquals(111, CalculatorKit.createCalculator(sessionId, null).evalValue("sheet1!A1")); + Assert.assertEquals(222, CalculatorKit.createCalculator(sessionId, null).evalValue("sheet2!A1")); + Assert.assertEquals(333, CalculatorKit.createCalculator(sessionId, null).evalValue("SUM(sheet1!A1,sheet2!A1)")); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } finally { + CellElementValueConverterRegistry.reset(); + } + Assert.assertNotNull(tpl.getReport(0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/fanruan/api/cal/CalculatorKitTest.java b/src/test/java/com/fanruan/api/cal/CalculatorKitTest.java new file mode 100644 index 0000000..595964c --- /dev/null +++ b/src/test/java/com/fanruan/api/cal/CalculatorKitTest.java @@ -0,0 +1,40 @@ +package com.fanruan.api.cal; + +import com.fr.general.FRLogger; +import com.fr.stable.script.CalculatorProvider; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * @ClassName CalculatorKitTest + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + */ +public class CalculatorKitTest { + @Test + public void testCalculatorCreate() { + CalculatorProvider calculator = CalculatorKit.createCalculator(); + try { + Assert.assertEquals(3, calculator.evalValue("sum(1,2)")); + } catch (Exception e) { + FRLogger.getLogger().error(e.getMessage(), e); + } + } + + @Test + public void testCalculatorCreateWithPara() { + Map paraMap = new HashMap(); + paraMap.put("a", 1); + paraMap.put("b", 2); + CalculatorProvider calculator = CalculatorKit.createCalculator(paraMap); + try { + Assert.assertEquals(3, calculator.evalValue("sum(a,b)")); + } catch (Exception e) { + FRLogger.getLogger().error(e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/fanruan/api/cal/FormulaKitTest.java b/src/test/java/com/fanruan/api/cal/FormulaKitTest.java index 17c96f8..997712c 100644 --- a/src/test/java/com/fanruan/api/cal/FormulaKitTest.java +++ b/src/test/java/com/fanruan/api/cal/FormulaKitTest.java @@ -2,17 +2,17 @@ package com.fanruan.api.cal; import com.fanruan.api.Prepare; import com.fanruan.api.err.KitError; +import com.fr.base.Formula; import com.fr.base.ParameterMapNameSpace; import com.fr.script.Calculator; import com.fr.stable.script.CalculatorProvider; -import com.fr.stable.script.NameSpace; import org.junit.Assert; import org.junit.Test; import java.util.HashMap; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * @author richie @@ -43,4 +43,18 @@ public class FormulaKitTest extends Prepare { Assert.fail(); } } + @Test + public void testCheckFormulaContent() { + assertEquals(true, FormulaKit.checkFormulaContent(new Formula(""))); + assertEquals(true, FormulaKit.checkFormulaContent(new Formula("SUM(1,1)"))); + + + assertEquals(true, FormulaKit.checkFormulaContent("")); + assertEquals(false, FormulaKit.checkFormulaContent(("=SUM(1,1)"))); + assertEquals(true, FormulaKit.checkFormulaContent(("SUM(1,1)"))); + + + assertEquals(true, FormulaKit.checkFormulaContent(new Formula("="))); + assertEquals(false, FormulaKit.checkFormulaContent("=")); + } } \ No newline at end of file diff --git a/src/test/java/com/fanruan/api/function/FunctionKitTest.java b/src/test/java/com/fanruan/api/function/FunctionKitTest.java new file mode 100644 index 0000000..f49cb0c --- /dev/null +++ b/src/test/java/com/fanruan/api/function/FunctionKitTest.java @@ -0,0 +1,46 @@ +package com.fanruan.api.function; + +import com.fanruan.api.Prepare; +import com.fanruan.api.function.shell.FineFunc; +import com.fr.script.Calculator; +import com.fr.stable.UtilEvalError; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * @ClassName FunctionKitTest + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + */ +public class FunctionKitTest extends Prepare { + + @Test + public void testFuncAdd() { + FunctionKit.removeFunc("com.fanruan.api.function.TestFunc"); + FunctionKit.addFunc(new FineFunc("TestFunc", "com.fanruan.api.function.TestFunc")); + try { + assertEquals("OK", Calculator.createCalculator().evalValue("TESTFUNC()")); + } catch (UtilEvalError utilEvalError) { + + } + + } + + @Test + public void testFuncRemove() { + FunctionKit.removeFunc("com.fanruan.api.function.TestFunc"); + FunctionKit.addFunc(new FineFunc("TestFunc", "com.fanruan.api.function.TestFunc")); + FunctionKit.removeFunc("com.fr.sdk.FuncTest"); + String result = "ERROR"; + try { + result = Calculator.createCalculator().evalValue("TESTFUNC()").toString(); + } catch (Throwable e) { + + } + assertNotEquals("OK", result); + } + +} \ No newline at end of file diff --git a/src/test/java/com/fanruan/api/function/TestFunc.java b/src/test/java/com/fanruan/api/function/TestFunc.java new file mode 100644 index 0000000..d39c8ee --- /dev/null +++ b/src/test/java/com/fanruan/api/function/TestFunc.java @@ -0,0 +1,10 @@ +package com.fanruan.api.function; + +import com.fr.script.AbstractFunction; + +public class TestFunc extends AbstractFunction { + @Override + public Object run(Object[] objects) { + return "OK"; + } +} \ No newline at end of file diff --git a/src/test/java/com/fanruan/api/report/analy/AnalyKitTest.java b/src/test/java/com/fanruan/api/report/analy/AnalyKitTest.java new file mode 100644 index 0000000..79d3eaf --- /dev/null +++ b/src/test/java/com/fanruan/api/report/analy/AnalyKitTest.java @@ -0,0 +1,31 @@ +package com.fanruan.api.report.analy; + +import com.fanruan.api.Prepare; +import com.fanruan.api.report.analy.data.TreeNode; +import com.fr.log.FineLoggerFactory; +import com.fr.main.impl.WorkBook; +import com.fr.main.workbook.ResultWorkBook; +import com.fr.stable.ActorConstants; +import com.fr.stable.ActorFactory; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class AnalyKitTest extends Prepare { + @Test + public void testTree() { + WorkBook tpl = new WorkBook(); + Map nodeMap = new HashMap(0); + try { + tpl.readStream(AnalyKitTest.class.getResourceAsStream("/com/fanruan/api/report/tree.cpt")); + ResultWorkBook resultWorkBook = tpl.execute(java.util.Collections.EMPTY_MAP, ActorFactory.getActor(ActorConstants.TYPE_ANALYSIS)); + nodeMap = AnalyKit.generateResultBookTree(resultWorkBook, 0); + + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + Assert.assertEquals(nodeMap.toString(), "{0=0#-1#[-1,1,3,8,14,19,25,31], 1=1#0#[-1,2], 3=3#0#[-1,4,5,6,7], 19=19#0#[-1,20,21,22,23,24], 8=8#0#[-1,9,10,11,12,13], 25=25#0#[-1,26,27,28,29,30], 14=14#0#[-1,15,16,17,18], 31=31#0#[-1,32,33,34,35,36,37,38,39]}"); + } +} \ No newline at end of file diff --git a/src/test/java/com/fanruan/api/util/TransmissionKitTest.java b/src/test/java/com/fanruan/api/util/TransmissionKitTest.java new file mode 100644 index 0000000..71db8f0 --- /dev/null +++ b/src/test/java/com/fanruan/api/util/TransmissionKitTest.java @@ -0,0 +1,27 @@ +package com.fanruan.api.util; + +import com.fanruan.api.util.shell.BatchSmsBody; +import com.fanruan.api.util.shell.SingleSmsBody; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @ClassName TransmissionKitTest + * @Author zack + * @Date 2019/8/23 + * @Version 10.0 + */ +public class TransmissionKitTest { + @Test + public void testSmsBody() { + try { + SingleSmsBody.newBuilder().mobile("").para(null).build(); + BatchSmsBody.newBuilder().mobiles(null).build(); + fail(); + } catch (Exception e) { + assertTrue(true); + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/fanruan/api/report/sheets.cpt b/src/test/resources/com/fanruan/api/report/sheets.cpt new file mode 100644 index 0000000..35de499 --- /dev/null +++ b/src/test/resources/com/fanruan/api/report/sheets.cpt @@ -0,0 +1,72 @@ + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
diff --git a/src/test/resources/com/fanruan/api/report/tree.cpt b/src/test/resources/com/fanruan/api/report/tree.cpt new file mode 100644 index 0000000..5418b34 --- /dev/null +++ b/src/test/resources/com/fanruan/api/report/tree.cpt @@ -0,0 +1,247 @@ + + + + + + + + + + + + +&F.>LRf]A0MJBQQ"KZHgQo8EMn +@7Hs(LfRWKV((C!-'"\IYO1394-67'^A,&J*BPY$tY:*s+7EK'XR4959PkQa +-MJK'B.-h@+"Lk%jc3KmJ1K;M[I;_Ze`8` +E#*u'-WX$SVu6f~ +]]> + + + + + + + + + + +Z[/8@QbMcc[;mbOAMAZ:E-bT]Ar`0YH#$D`bg'@npmN;JkUIYbSDj]ASjZRrk$=B/?jp7jY2+ +qcnjdN=QaN'#B`0CHe*XmCkRB7@ni-'(k((1bLt0IXa+Y'e#`pddA+O +AiC#1J.qQtQC7]AKlE8;tbMa*_@ +tVW13!Oj@[6Uf]Am^>'3aS$(+s1MVqXY)YhY[B~ +]]> + + + + + + + + + + +Z[/8@QbMcc[;mbOAMAZ:E-bT]Ar`0YH#$D`bg'@npmN;JkUIYbSDj]ASjZRrk$=B/?jp7jY2+ +qcnjdN=QaN'#B`0CHe*XmCkRB7@ni-'(k((1bLt0IXa+Y'e#`pddA+O +AiC#1J.qQtQC7]AKlE8;tbMa*_@ +tVW13!Oj@[6Uf]Am^>'3aS$(+s1MVqXY)YhY[B~ +]]> + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+ + + + +
+
+ +
+ + + + + + + + + + + + + + + +