Commits

dloy committed 8122c2c

Test multithread

Comments (0)

Files changed (3)

fixity-storeload/pom.xml

                             </artifactSet>
                             <transformers>
                                 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                                    <mainClass>org.cdlib.mrt.fixity.tools.Store2FixityList</mainClass>
+                                    <mainClass>org.cdlib.mrt.fixity.tools.Store2FixityListMulti</mainClass>
                                 </transformer>
                             </transformers>
 

fixity-storeload/src/main/java/org/cdlib/mrt/fixity/tools/Store2FixityListMulti.java

+
+/*********************************************************************
+    Copyright 2003 Regents of the University of California
+    All rights reserved
+*********************************************************************/
+
+package org.cdlib.mrt.fixity.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Properties;
+import java.util.Vector;
+
+
+import org.cdlib.mrt.utility.TException;
+import org.cdlib.mrt.utility.LoggerInf;
+import org.cdlib.mrt.utility.StringUtil;
+
+import org.cdlib.mrt.core.ComponentContent;
+import org.cdlib.mrt.utility.TFileLogger;
+import org.cdlib.mrt.utility.TFrame;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Load manifest.
+ * @author  dloy
+ */
+
+public class Store2FixityListMulti
+{
+    private static final String NAME = "Store2FixityListMulti";
+    private static final String MESSAGE = NAME + ": ";
+
+    private static final String NL = System.getProperty("line.separator");
+    private static final boolean DEBUG = true;
+    protected LoggerInf logger = null;
+    //protected FixityItemDB db = null;
+    protected ComponentContent content = null;
+    //protected Manifest manifest = null;
+    protected File listFile = null;
+    protected File baseFile = null;
+    protected String baseURL = null;
+    protected String serverLink = null;
+    protected String cmdTypeS = null;
+    protected BufferedReader reader = null;
+    protected Vector <String> exArr = new Vector <String> (100);
+    protected int maxExceptions = 100;
+    protected int maxout = 10;
+    protected int threadCnt = 0;
+
+    public Store2FixityListMulti(
+            int maxout,
+            File listFile,
+            File baseFile,
+            String baseURL,
+            String serverLink,
+            String cmdTypeS,
+            int threadCnt,
+            LoggerInf logger)
+        throws TException
+    {
+        this.maxout = maxout;
+        this.baseFile = baseFile;
+        this.baseURL = baseURL;
+        this.logger = logger;
+        this.listFile = listFile;
+        this.serverLink = serverLink;
+        this.cmdTypeS = cmdTypeS;
+        this.threadCnt = threadCnt;
+        validate();
+    }
+
+    protected void validate()
+        throws TException
+    {
+        if (StringUtil.isEmpty(serverLink)) {
+            throw new TException.INVALID_OR_MISSING_PARM("serverLink required");
+        }
+        try {
+            URL link = new URL(serverLink);
+        } catch (Exception ex) {
+            throw new TException.INVALID_OR_MISSING_PARM("serverLink invalid:" + serverLink);
+        }
+        if (logger == null) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing logger");
+        }
+        if (baseFile == null) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing baseFile");
+        }
+        if (!baseFile.exists()) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "baseFile does not exist");
+        }
+        if (StringUtil.isEmpty(baseURL)) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing baseURL");
+        }
+        if (listFile == null) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing listFile");
+        }
+        if (!listFile.exists()) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "listFile does not exist");
+        }
+        if (StringUtil.isEmpty(cmdTypeS)) {
+            cmdTypeS = "add";
+        }
+        if (threadCnt == 0) threadCnt = 1;
+        setList();
+    }
+
+    protected void setList()
+        throws TException
+    {
+        try {
+            FileInputStream inStream = new FileInputStream(listFile);
+            DataInputStream in = new DataInputStream(inStream);
+            reader = new BufferedReader(new InputStreamReader(in, "utf-8"));
+
+
+        } catch (Exception ex) {
+            throw new TException(ex);
+        }
+    }
+
+    private void run()
+        throws TException
+    {
+        try {
+            ExecutorService threadPool
+                    = Executors.newFixedThreadPool(threadCnt);
+            for (int iOut=0; iOut < maxout; iOut++) {
+                String line = reader.readLine();
+                if (line == null) {
+                    break;
+                }
+                System.out.println("Line:" + line);
+                Store2FixityThread s2f = new Store2FixityThread(baseFile, baseURL, serverLink, cmdTypeS, line, logger);
+                threadPool.execute(s2f);
+                try {
+                    s2f.run();
+                    
+                } catch (Exception ex) {
+                    String exception = line + "::" + ex;
+                    exArr.add(exception);
+                    if (exArr.size() > maxExceptions) {
+                        dumpException();
+                        throw new TException.GENERAL_EXCEPTION(MESSAGE + "max exceptions");
+                    }
+                }
+                
+            }
+
+        } catch (TException fe) {
+            throw fe;
+
+        } catch(Exception e)  {
+            if (logger != null)
+            {
+                logger.logError(
+                    "Main: Encountered exception:" + e, 0);
+                logger.logError(
+                        StringUtil.stackTrace(e), 10);
+            }
+            throw new TException(e);
+
+        } finally {
+            try {
+                reader.close();
+            } catch (Exception ex) { }
+        }
+    }
+
+    protected void dumpException()
+    {
+        for (int i=0; i<exArr.size(); i++) {
+            String dmp = exArr.get(i);
+            System.out.println("exc(" + i + "):" + dmp);
+        }
+    }
+    protected void log(String msg)
+    {
+        if (!DEBUG) return;
+        System.out.println(msg);
+    }
+
+    protected String dump(String header)
+    {
+        StringBuffer buf = new StringBuffer();
+        try {
+            buf.append("**" + header + "**" + NL
+                    + " - maxExceptions=" + maxExceptions + NL
+                    + " - baseFile=" + baseFile.getCanonicalPath() + NL
+                    + " - baseFile=" + listFile.getCanonicalPath() + NL
+                    + " - baseURL=" + baseURL + NL
+                    + " - cmdTypeS=" + cmdTypeS + NL
+                    + " - serverLink=" + serverLink + NL
+                    + " - threadCnt=" + threadCnt + NL
+                    + " - Exception count=" + exArr.size() + NL
+                    );
+            return buf.toString();
+            
+        } catch (Exception ex) {
+            System.out.println(MESSAGE + "dump exception:" + ex);
+            return ex.toString();
+        }
+    }
+
+
+    /**
+     * Main method
+     */
+    public static void main(String args[])
+    {
+
+        TFrame tFrame = null;
+        try {
+            String propertyList[] = {
+                "resources/FixityStoreLoad.properties"};
+            tFrame = new TFrame(propertyList, "FixityStoreLoad");
+            Properties prop  = tFrame.getProperties();
+
+            // Create an instance of this object
+            LoggerInf logger = new TFileLogger(NAME, 50, 50);
+
+            String listFileS = get(prop, "listFile");
+            File listFile = new File(listFileS);
+            if (!listFile.exists()) {
+                throw new TException.INVALID_OR_MISSING_PARM("listFile does not exist:" + listFileS);
+            }
+            String baseFileS = get(prop, "baseFile");
+            File baseFile = new File(baseFileS);
+
+            String serverLink = get(prop, "serverLink");
+
+            String cmdTypeS = prop.getProperty("cmdType");
+
+            String maxoutS = prop.getProperty("maxout", "10");
+            int maxout = Integer.parseInt(maxoutS);
+
+            String baseURL = get(prop, "baseURL");
+
+            String threadCntS = prop.getProperty("threadCnt", "1");
+            int threadCnt = threadCnt = Integer.parseInt(threadCntS);
+            Store2FixityListMulti test = new Store2FixityListMulti(
+                maxout,
+                listFile,
+                baseFile,
+                baseURL,
+                serverLink,
+                cmdTypeS,
+                threadCnt,
+                logger);
+            System.out.println(test.dump("MAIN dump"));
+            test.run();
+
+        } catch(Exception e) {
+                System.out.println(
+                    "Main: Encountered exception:" + e);
+                System.out.println(
+                        StringUtil.stackTrace(e));
+        }
+    }
+
+
+    protected static String get(Properties prop, String key)
+        throws TException
+    {
+        String retVal = prop.getProperty(key);
+        if (StringUtil.isEmpty(retVal)) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "missing property:" + key);
+        }
+        return retVal;
+    }
+}

fixity-storeload/src/main/java/org/cdlib/mrt/fixity/tools/Store2FixityThread.java

+
+/*********************************************************************
+    Copyright 2003 Regents of the University of California
+    All rights reserved
+*********************************************************************/
+
+package org.cdlib.mrt.fixity.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.URL;
+import java.util.Properties;
+import java.util.Vector;
+
+
+import org.cdlib.mrt.utility.TException;
+import org.cdlib.mrt.utility.LoggerInf;
+import org.cdlib.mrt.utility.StringUtil;
+
+import org.cdlib.mrt.core.FileComponent;
+import org.cdlib.mrt.core.FixityClient;
+import org.cdlib.mrt.core.ComponentContent;
+import org.cdlib.mrt.core.Identifier;
+import org.cdlib.mrt.core.Manifest;
+import org.cdlib.mrt.core.ManifestRowAbs;
+import org.cdlib.mrt.core.MessageDigest;
+import org.cdlib.mrt.utility.FileUtil;
+import org.cdlib.mrt.utility.TFileLogger;
+import org.cdlib.mrt.utility.TFrame;
+import org.cdlib.mrt.utility.URLEncoder;
+
+/**
+ * Load manifest.
+ * @author  dloy
+ */
+
+public class Store2FixityThread
+    implements Runnable
+{
+    private static final String NAME = "Store2FixityThread";
+    private static final String MESSAGE = NAME + ": ";
+    public enum CmdType {add, queue};
+
+    private static final String NL = System.getProperty("line.separator");
+    private static final boolean DEBUG = true;
+    protected LoggerInf logger = null;
+    //protected FixityItemDB db = null;
+    protected String line = null;
+    protected Identifier id = null;
+    protected int version = -1;
+    protected ComponentContent content = null;
+    //protected Manifest manifest = null;
+    protected File baseFile = null;
+    protected String baseURL = null;
+    protected int maxout = 10000;
+    protected FixityClient client = null;
+    protected String serverLink = null;
+    protected String[] owners = null;
+    protected String[] members = null;
+    protected CmdType cmdType = null;
+    protected TException exception = null;
+
+    public Store2FixityThread(
+            File baseFile,
+            String baseURL,
+            String serverLink,
+            String cmdS,
+            String line,
+            LoggerInf logger)
+        throws TException
+    {
+        this.baseFile = baseFile;
+        this.baseURL = baseURL;
+        this.logger = logger;
+        this.line = line;
+        this.serverLink = serverLink;
+        this.client = new FixityClient(logger);
+        validate(cmdS);
+    }
+
+    protected void validate(String cmdS)
+        throws TException
+    {
+        if (StringUtil.isEmpty(serverLink)) {
+            throw new TException.INVALID_OR_MISSING_PARM("serverLink required");
+        }
+        try {
+            URL link = new URL(serverLink);
+        } catch (Exception ex) {
+            throw new TException.INVALID_OR_MISSING_PARM("serverLink invalid:" + serverLink);
+        }
+        if (logger == null) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing logger");
+        }
+        if (baseFile == null) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing baseFile");
+        }
+        if (!baseFile.exists()) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "baseFile does not exist");
+        }
+        if (StringUtil.isEmpty(baseURL)) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing baseURL");
+        }
+        if (StringUtil.isEmpty(line)) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "Missing line");
+        }
+        if (StringUtil.isEmpty(cmdS)) {
+            cmdS = "add";
+        }
+        try {
+            cmdS = cmdS.toLowerCase();
+            cmdType = CmdType.valueOf(cmdS);
+        } catch (Exception ex) {
+            cmdType = CmdType.add;
+        }
+        setLine();
+    }
+
+    protected void setLine()
+        throws TException
+    {
+        try {
+            String [] names = line.split("/");
+            if (names.length == 0) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "line not delimited by /");
+            }
+            
+            String arkp = null;
+            for (String name : names) {
+                if (name.startsWith("ark")) {
+                    arkp = name;
+                    break;
+                }
+            }
+            if (arkp == null) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "ark not found");
+            }
+            id = getArk(arkp);
+            if (id == null) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "ark not found in line");
+            }
+            if (arkp == null) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "ark not found");
+            }
+            id = getArk(arkp);
+            if (id == null) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "ark not found in line");
+            }
+
+            String versionS = null;
+            for (String name : names) {
+                if (name.matches("[Vv]\\d\\d\\d")) {
+                    versionS = name.substring(1);
+                    break;
+                }
+            }
+            if (versionS == null) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "version not found in line");
+            }
+            try {
+                version = Integer.parseInt(versionS);
+            } catch (Exception ex) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "version format not valid:" + versionS);
+            }
+
+            content = getComponentContent(baseFile, line);
+            if (content == null) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "not able to process manifest");
+            }
+            extractMembership();
+            extractOwner();
+
+        } catch (Exception ex) {
+            throw new TException(ex);
+        }
+    }
+
+    public static Identifier getArk(String arkP)
+        throws TException
+    {
+        if (StringUtil.isEmpty(arkP)) return null;
+        StringBuffer buf = new StringBuffer(arkP.length());
+        char c = 0;
+        for (int i=0; i<arkP.length(); i++) {
+            c = arkP.charAt(i);
+            if (c == '+') buf.append(":");
+            else if (c == '=') buf.append("/");
+            else buf.append(c);
+        }
+        Identifier id = new Identifier(buf.toString());
+        return id;
+    }
+    
+    public ComponentContent getComponentContent(File baseFile, String line)
+        throws TException
+    {
+        ComponentContent retContent = null;
+        FileInputStream inStream = null;
+        try {
+            int pos = line.indexOf("/manifest.txt");
+            if (pos < 0) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "manifest.txt not found in line");
+            }
+            File manifestFile = new File(baseFile, line);
+            if (!manifestFile.exists()) {
+                throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "manifest.txt file does not exist:" + manifestFile.getCanonicalPath());
+            }
+             
+            Manifest manifest = Manifest.getManifest(logger, ManifestRowAbs.ManifestType.object);
+            inStream = new FileInputStream(manifestFile);
+            retContent = new ComponentContent(logger, manifest, inStream);
+            return retContent;
+
+        } catch (Exception ex) {
+            throw new TException(ex);
+
+        } finally {
+            try {
+                if (inStream != null) inStream.close();
+            } catch (Exception ex) { }
+        }
+    }
+    /**
+     * Main method
+     */
+    public static void main(String args[])
+    {
+
+        TFrame tFrame = null;
+        try {
+            String propertyList[] = {
+                "resources/FixityTest.properties"};
+            tFrame = new TFrame(propertyList, "TestFixity");
+            Properties prop  = tFrame.getProperties();
+
+            // Create an instance of this object
+            LoggerInf logger = new TFileLogger(NAME, 50, 50);
+            String baseFileS = get(prop, NAME + ".baseFile");
+            File baseFile = new File(baseFileS);
+            if (!baseFile.exists()) {
+                throw new TException.INVALID_OR_MISSING_PARM("baseFile does not exist:" + baseFile.getCanonicalPath());
+            }
+            String line = get(prop, NAME + ".line");
+            String serverLink = get(prop, NAME + ".serverLink");
+            String baseURL = get(prop, NAME + ".baseURL");
+
+            Store2FixityThread test = new Store2FixityThread(
+                baseFile,
+                baseURL,
+                serverLink,
+                "add",
+                line,
+                logger);
+            System.out.println(test.dump("MAIN dump"));
+            if (true) return;
+            test.run();
+
+        } catch(Exception e) {
+                System.out.println(
+                    "Main: Encountered exception:" + e);
+                System.out.println(
+                        StringUtil.stackTrace(e));
+        }
+    }
+
+    protected static String get(Properties prop, String key)
+        throws TException
+    {
+        String retVal = prop.getProperty(key);
+        if (StringUtil.isEmpty(retVal)) {
+            throw new TException.INVALID_OR_MISSING_PARM(MESSAGE + "missing property:" + key);
+        }
+        return retVal;
+    }
+
+    public void run()
+    {
+        try {
+            Vector<FileComponent> components = content.getFileComponents();
+            for (FileComponent component : components) {
+                sendFixity(component);
+            }
+
+        } catch (TException fe) {
+            exception = fe;
+
+        } catch(Exception e)  {
+            if (logger != null)
+            {
+                logger.logError(
+                    "Main: Encountered exception:" + e, 0);
+                logger.logError(
+                        StringUtil.stackTrace(e), 10);
+            }
+            exception = new TException(e);
+
+        }
+    }
+
+    protected void sendFixity(FileComponent component)
+        throws TException
+    {
+        try {
+            MessageDigest message = component.getMessageDigest();
+            long size = component.getSize();
+            String urlS = baseURL
+                    + "/" + URLEncoder.encode(id.getValue(), "utf-8")
+                    + "/" + version
+                    + "/" + URLEncoder.encode(component.getIdentifier(), "utf-8");
+            String linkS = serverLink;
+            int timeout = 30000;
+            int retry = 3;
+            String source = "web";
+            String sizeS = "" + size;
+            String digestType = message.getJavaAlgorithm();
+            String digestValue = message.getValue();
+            String context = "|objectid=" + id.getValue()
+                    + "|versionid=" + version
+                    + "|fileid=" + component.getIdentifier()
+                    + "|";
+
+            String note = null;
+            String formatTypeS = "XML";
+            Properties prop = null;
+            if (cmdType == CmdType.add) {
+                prop = client.add(
+                    linkS,
+                    timeout,
+                    retry,
+                    urlS,
+                    source,
+                    sizeS,
+                    digestType,
+                    digestValue,
+                    context,
+                    note,
+                    formatTypeS);
+
+            } else if (cmdType == CmdType.queue) {
+                prop = client.queue(
+                    linkS,
+                    timeout,
+                    retry,
+                    urlS,
+                    source,
+                    sizeS,
+                    digestType,
+                    digestValue,
+                    context,
+                    note,
+                    formatTypeS);
+
+            } else {
+                throw new TException.REQUEST_INVALID(
+                        MESSAGE + "command type not supported:" + cmdType);
+            }
+            if (isError(prop)) {
+                handleException(prop);
+            }
+
+            for (String member: members) {
+                String memberContext = "|member=" + member + "|";
+                Properties contextProp = client.update(
+                    linkS,
+                    timeout,
+                    retry,
+                    urlS,
+                    null,
+                    null,
+                    null,
+                    null,
+                    memberContext,
+                    null,
+                    formatTypeS);
+                if (isError(contextProp)) {
+                    handleException(prop);
+                }
+            }
+
+            for (String owner: owners) {
+                String ownerContext = "|owner=" + owner + "|";
+                Properties contextProp = client.update(
+                    linkS,
+                    timeout,
+                    retry,
+                    urlS,
+                    null,
+                    null,
+                    null,
+                    null,
+                    ownerContext,
+                    null,
+                    formatTypeS);
+                if (isError(contextProp)) {
+                    handleException(prop);
+                }
+            }
+
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            throw new TException(ex);
+        }
+
+    }
+
+    protected void handleException(Properties prop)
+        throws TException
+    {
+        if ((prop == null) || (prop.size() == 0)) {
+            throw new TException.INVALID_OR_MISSING_PARM("No properties returned from FixityClient");
+        }
+        String exceptionS = prop.getProperty("response.state");
+        if (StringUtil.isEmpty(exceptionS)) {
+            throw new TException.INVALID_OR_MISSING_PARM("No exception state retunred from FixityClient");
+        }
+        if (exceptionS.contains("exc:tException.REQUEST_ITEM_EXISTS")) {
+            throw new TException.REQUEST_ITEM_EXISTS(exceptionS);
+        } else {
+            throw new TException.GENERAL_EXCEPTION(exceptionS);
+        }
+    }
+
+    protected int getStatus(Properties prop)
+    {
+        int statusVal = 200;
+        String statusValS = prop.getProperty("response.status");
+        if (StringUtil.isNotEmpty(statusValS)) {
+            statusVal = Integer.parseInt(statusValS);
+        }
+        return statusVal;
+    }
+
+
+
+    protected boolean isError(Properties prop)
+    {
+        if (prop == null) return true;
+
+        String errorStatus = prop.getProperty("error.status");
+        if (StringUtil.isNotEmpty(errorStatus)) return true;
+        return false;
+    }
+
+    protected void log(String msg)
+    {
+        if (!DEBUG) return;
+        System.out.println(msg);
+    }
+
+    protected String dump(String header)
+    {
+        StringBuffer buf = new StringBuffer();
+        try {
+            buf.append("**" + header + "**" + NL
+                    + " - line=" + line + NL
+                    + " - id=" + id.getValue() + NL
+                    + " - version=" + version + NL
+                    + " - baseFile=" + baseFile.getCanonicalPath() + NL
+                    + " - baseURL=" + baseURL + NL
+                    + " - serverLink=" + serverLink + NL
+                    + " - cmdType=" + cmdType.toString() + NL
+                    );
+            if (members != null) {
+                for (String item: members) {
+                    buf.append(" - member=" + item + NL);
+                }
+            }
+            if (owners != null) {
+                for (String item: owners) {
+                    buf.append(" - owner=" + item + NL);
+                }
+            }
+            return buf.toString();
+            
+        } catch (Exception ex) {
+            System.out.println(MESSAGE + "dump exception:" + ex);
+            return ex.toString();
+        }
+    }
+
+    protected void extractMembership()
+        throws TException
+    {
+        try {
+            String urlS = baseURL
+                    + "/" + URLEncoder.encode(id.getValue(), "utf-8")
+                    + "/" + version
+                    + "/" + URLEncoder.encode("system/mrt-membership.txt", "utf-8");
+            members = FileUtil.getLinesFromURL(logger, urlS);
+
+        } catch (Exception ex) {
+            log("Warning - getMembership Exception:" + ex);
+        }
+
+    }
+
+    protected void extractOwner()
+        throws TException
+    {
+        try {
+            String urlS = baseURL
+                    + "/" + URLEncoder.encode(id.getValue(), "utf-8")
+                    + "/" + version
+                    + "/" + URLEncoder.encode("system/mrt-owner.txt", "utf-8");
+            owners = FileUtil.getLinesFromURL(logger, urlS);
+
+        } catch (Exception ex) {
+            log("Warning - getMembership Exception:" + ex);
+        }
+
+    }
+
+    public String[] getMembers() {
+        return members;
+    }
+
+    public String[] getOwners() {
+        return owners;
+    }
+
+    public TException getException() {
+        return exception;
+    }
+
+
+}