Package org.apache.commons.transaction.file

Source Code of org.apache.commons.transaction.file.FileResourceManagerTest

/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//transaction/src/test/org/apache/commons/transaction/file/FileResourceManagerTest.java,v 1.3 2005/01/13 01:34:25 ozeigermann Exp $
<<<<<<< .mine
* $Revision: 1.3 $
* $Date: 2005-02-26 14:16:14 +0100 (Sa, 26 Feb 2005) $
=======
* $Revision$
* $Date: 2005-02-26 14:16:14 +0100 (Sa, 26 Feb 2005) $
>>>>>>> .r168169
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.apache.commons.transaction.file;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.logging.Logger;

import javax.transaction.Status;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.commons.transaction.util.FileHelper;
import org.apache.commons.transaction.util.Jdk14Logger;
import org.apache.commons.transaction.util.LoggerFacade;
import org.apache.commons.transaction.util.RendezvousBarrier;

/**
* Tests for FileResourceManager.
*
* @version $Revision$
*/
public class FileResourceManagerTest extends TestCase {

    private static final Logger logger = Logger.getLogger(FileResourceManagerTest.class.getName());
    private static final LoggerFacade sLogger = new Jdk14Logger(logger);

    private static final String STORE = "tmp/store";
    private static final String WORK = "tmp/work";
    private static final String ENCODING = "ISO-8859-15";
    // FIXME
    // XXX INCREASE THIS WHEN DEBUGGING OTHERWISE THE BARRIER WILL TIME OUT AFTER TWO SECONDS
    // MOST LIKELY CONFUSING YOU COMPLETELY
    private static final long BARRIER_TIMEOUT = 200000;

    private static final String[] INITIAL_FILES = new String[] { STORE + "/olli/Hubert6", STORE + "/olli/Hubert" };

    private static final String STATUS_COMMITTING_CONTEXT =
        "8\n10\n2000\n1063099404687\n";
    private static final String[] STATUS_COMMITTING_CONTEXT_CHANGE_FILES =
        new String[] { "olli/Hubert40", "olli/Hubert50" };
    private static final String[] STATUS_COMMITTING_CONTEXT_DELETE_FILES = new String[] { "/olli/Hubert" };
    private static final String[] STATUS_COMMITTING_CONTEXT_RESULT_FILES =
        new String[] { "Hubert6", "Hubert50", "Hubert40" };

    private static void initCommittingRecovery() throws Throwable {
        String txId = "COMMITTING";
        createTxContextFile(txId, STATUS_COMMITTING_CONTEXT);
        createTxDeleteFiles(txId, STATUS_COMMITTING_CONTEXT_DELETE_FILES);
        createTxChangeFiles(txId, STATUS_COMMITTING_CONTEXT_CHANGE_FILES);
    }

    private static final String STATUS_COMMITTED_CONTEXT =
        "3\n10\n2000\n1063099404687\n";
    private static final String[] STATUS_COMMITTED_CONTEXT_CHANGE_FILES =
        new String[] { "olli/Hubert4", "olli/Hubert5" };
    private static final String[] STATUS_COMMITTED_CONTEXT_DELETE_FILES = new String[] {
    };
    private static final String[] STATUS_COMMITTED_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };

    protected static final long TIMEOUT = Long.MAX_VALUE;

    private static int deadlockCnt = 0;

    private static void initCommittedRecovery() throws Throwable {
        String txId = "COMMITTED";
        createTxContextFile(txId, STATUS_COMMITTED_CONTEXT);
        createTxDeleteFiles(txId, STATUS_COMMITTED_CONTEXT_DELETE_FILES);
        createTxChangeFiles(txId, STATUS_COMMITTED_CONTEXT_CHANGE_FILES);
    }

    private static final String STATUS_ROLLING_BACK_CONTEXT =
        "9\n10\n2000\n1063099404687\n";
    private static final String[] STATUS_ROLLING_BACK_CONTEXT_CHANGE_FILES =
        new String[] { "olli/Hubert4", "olli/Hubert5" };
    private static final String[] STATUS_ROLLING_BACK_CONTEXT_DELETE_FILES = new String[] {
    };
    private static final String[] STATUS_ROLLING_BACK_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };

    private static void initRollingBackRecovery() throws Throwable {
        String txId = "ROLLING_BACK";
        createTxContextFile(txId, STATUS_ROLLING_BACK_CONTEXT);
        createTxDeleteFiles(txId, STATUS_ROLLING_BACK_CONTEXT_DELETE_FILES);
        createTxChangeFiles(txId, STATUS_ROLLING_BACK_CONTEXT_CHANGE_FILES);
    }

    private static final String STATUS_ROLLEDBACK_CONTEXT =
        "4\n10\n2000\n1063099404687\n";
    private static final String[] STATUS_ROLLEDBACK_CONTEXT_CHANGE_FILES =
        new String[] { "olli/Hubert4", "olli/Hubert5" };
    private static final String[] STATUS_ROLLEDBACK_CONTEXT_DELETE_FILES = new String[] {
    };
    private static final String[] STATUS_ROLLEDBACK_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };

    private static void initRolledBackRecovery() throws Throwable {
        String txId = "ROLLEDBACK";
        createTxContextFile(txId, STATUS_ROLLEDBACK_CONTEXT);
        createTxDeleteFiles(txId, STATUS_ROLLEDBACK_CONTEXT_DELETE_FILES);
        createTxChangeFiles(txId, STATUS_ROLLEDBACK_CONTEXT_CHANGE_FILES);
    }

    private static final String STATUS_ACTIVE_CONTEXT = "0\n10\n2000\n1063099404687\n";
    private static final String[] STATUS_ACTIVE_CONTEXT_CHANGE_FILES = new String[] { "olli/Hubert4", "olli/Hubert5" };
    private static final String[] STATUS_ACTIVE_CONTEXT_DELETE_FILES = new String[] {
    };
    private static final String[] STATUS_ACTIVE_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };

    private static void initActiveRecovery() throws Throwable {
        String txId = "ACTIVE";
        createTxContextFile(txId, STATUS_ACTIVE_CONTEXT);
        createTxDeleteFiles(txId, STATUS_ACTIVE_CONTEXT_DELETE_FILES);
        createTxChangeFiles(txId, STATUS_ACTIVE_CONTEXT_CHANGE_FILES);
    }

    private static void removeRec(String dirPath) {
        FileHelper.removeRec(new File(dirPath));
    }

    private static final void createFiles(String[] filePaths) {
        createFiles(filePaths, null, null);
    }

    private static final void createFiles(String[] filePaths, String dirPath) {
        createFiles(filePaths, null, dirPath);
    }

    private static final void createFiles(String[] filePaths, String[] contents) {
        createFiles(filePaths, contents, null);
    }

    private static final void createFiles(String[] filePaths, String[] contents, String dirPath) {
        for (int i = 0; i < filePaths.length; i++) {
            String filePath = filePaths[i];
            File file;
            if (dirPath != null) {
                file = new File(new File(dirPath), filePath);
            } else {
                file = new File(filePath);
            }
            file.getParentFile().mkdirs();
            try {
                file.delete();
                file.createNewFile();
                String content = null;
                if (contents != null && contents.length > i) {
                    content = contents[i];
                }
                if (content != null) {
                    FileOutputStream stream = new FileOutputStream(file);
                    stream.write(contents[i].getBytes(ENCODING));
                    stream.close();
                }
            } catch (IOException e) {
            }
        }
    }

    private static final void deleteInDir(String dirPath, String[] fileNames) {
        File dir = new File(dirPath);

        if (dir.isDirectory()) {
            for (int i = 0; i < fileNames.length; i++) {
                String fileName = fileNames[i];
                File file = new File(dir, fileName);
                file.delete();
            }
        }
    }

    private static final void checkIsEmpty(String dirPath) {
        checkExactlyContains(dirPath, null);
    }
    private static final void checkExactlyContains(String dirPath, String[] fileNames) {
        checkExactlyContains(dirPath, fileNames, null);
    }

    private static final void checkExactlyContains(String dirPath, String[] fileNames,
            String[] contents) {
        File dir = new File(dirPath);

        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (fileNames == null) {
                if (files.length != 0) {
                    fail(dirPath + " must be empty");
                } else {
                    return;
                }
            }

            if (files.length != fileNames.length) {
                fail(dirPath + " contains " + files.length + " instead of " + fileNames.length
                        + " files");
            }

            for (int i = 0; i < fileNames.length; i++) {
                String fileName = fileNames[i];
                boolean match = false;
                File file = null;
                for (int j = 0; j < files.length; j++) {
                    file = files[j];
                    if (file.getName().equals(fileName)) {
                        match = true;
                        break;
                    }
                }
                if (!match) {
                    fail(dirPath + " does not contain required " + fileName);
                }

                String content = null;
                if (contents != null && i < contents.length) {
                    content = contents[i];
                }
                if (content != null && !compare(file, content)) {
                    fail("Contents of " + fileName + " in " + dirPath
                            + " does not contain required content '" + content + "'");
                }
            }

        } else {
            fail(dirPath + " is not directoy");
        }
    }

    private static boolean compare(FileInputStream stream, byte[] bytes) {
        int read;
        int count = 0;
        try {
            while ((read = stream.read()) != -1) {
                if (bytes[count++] != read) {
                    return false;
                }
            }
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    private static boolean compare(File file, String content) {
        FileInputStream stream = null;
        try {
            byte[] bytes = content.getBytes(ENCODING);
            stream = new FileInputStream(file);
            return compare(stream, bytes);
        } catch (Throwable t) {
            return false;
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private static String workForTx(Object txId) {
        return WORK + "/" + txId;
    }

    private static String changeForTx(Object txId) {
        return workForTx(txId) + "/change";
    }

    private static String deleteForTx(Object txId) {
        return workForTx(txId) + "/delete";
    }

    private static String logForTx(Object txId) {
        return workForTx(txId) + "/transaction.log";
    }

    private static void reset() {
        removeRec(STORE);
        removeRec(WORK);
        new File(STORE).mkdirs();
        new File(WORK).mkdirs();
    }

    private static void createInitialFiles() {
        createFiles(INITIAL_FILES);
    }

    private static void createTxContextFile(Object txId, String content) {
        createFiles(new String[] { logForTx(txId)}, new String[] { txId + "\n" + content });
    }

    private static void createTxDeleteFiles(Object txId, String[] files) {
        createFiles(files, deleteForTx(txId));
    }

    private static void createTxChangeFiles(Object txId, String[] files) {
        createFiles(files, changeForTx(txId));
    }

    // XXX need this, as JUnit seems to print only part of these strings
    private static void report(String should, String is) {
        if (!is.equals(should)) {
            fail("\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n");
        }
    }

    public static FileResourceManager createFRM() {
        return new FileResourceManager(STORE, WORK, false, sLogger, true);
    }

    public static Test suite() {
        TestSuite suite = new TestSuite(FileResourceManagerTest.class);
        return suite;
    }

    public static void main(java.lang.String[] args) {
        junit.textui.TestRunner.run(suite());
    }

    public FileResourceManagerTest(String testName) {
        super(testName);
    }

    public void testGlobal() throws Throwable {
        reset();
        createInitialFiles();
       
        final FileResourceManager rm = createFRM();
       
        rm.start();

        final RendezvousBarrier shutdownBarrier = new RendezvousBarrier("Shutdown", 3, BARRIER_TIMEOUT, sLogger);
        final RendezvousBarrier start2Barrier = new RendezvousBarrier("Start2", BARRIER_TIMEOUT, sLogger);
        final RendezvousBarrier commit1Barrier = new RendezvousBarrier("Commit1", BARRIER_TIMEOUT, sLogger);

        final Object txId1 = "Create";

        Thread create = new Thread(new Runnable() {
            public void run() {
                try {
                    rm.startTransaction(txId1);

                    shutdownBarrier.call();
                    start2Barrier.call();

                    rm.createResource(txId1, "/olli/Hubert4");
                    rm.createResource(txId1, "/olli/Hubert5");
                    String msg = "Greetings from " + txId1 + "\n";
                    OutputStream out = rm.writeResource(txId1, "/olli/Hubert6");
                    out.write(msg.getBytes(ENCODING));

                    commit1Barrier.meet();

                    checkExactlyContains(
                        changeForTx(txId1) + "/olli",
                        new String[] { "Hubert4", "Hubert5", "Hubert6" },
                        new String[] { "", "", "Greetings from " + txId1 + "\n" });

                    rm.commitTransaction(txId1);

                    checkExactlyContains(
                        STORE + "/olli",
                        new String[] { "Hubert", "Hubert4", "Hubert5", "Hubert6" },
                        new String[] { "", "", "", "Greetings from " + txId1 + "\n" });

                } catch (Throwable e) {
                    System.err.println("Error: " + e);
                    e.printStackTrace();
                }
            }
        }, "Create Thread");

        Thread modify = new Thread(new Runnable() {
            public void run() {
                Object txId = null;
                try {

                    {
                        InputStream in = rm.readResource("/olli/Hubert6");
                        BufferedReader reader = new BufferedReader(new InputStreamReader(in, ENCODING));
                        String line = reader.readLine();
                        assertEquals(line, null);
                        in.close();
                    }

                    txId = "Modify";
                    rm.startTransaction(txId);
                    rm.setIsolationLevel(txId, ResourceManager.ISOLATION_LEVEL_READ_COMMITTED);

                    {
                        InputStream in = rm.readResource(txId, "/olli/Hubert6");
                        BufferedReader reader = new BufferedReader(new InputStreamReader(in, ENCODING));
                        String line = reader.readLine();
                        assertEquals(line, null);
                        in.close();
                    }

                    shutdownBarrier.call();

                    rm.createResource(txId, "/olli/Hubert1");
                    rm.createResource(txId, "/olli/Hubert2");
                    rm.createResource(txId, "/olli/Hubert3");

                    // wait until tx commits, so there already are Hubert4 and Hubert5 and
                    // Hubert6 changes
                    commit1Barrier.meet();

                    rm.createResource(txId, "/olli/Hubert4");
                    rm.createResource(txId, "/olli/Hubert5");

                    rm.createResource(txId, "/olli/Hubert6");
                    InputStream in = rm.readResource(txId, "/olli/Hubert6");
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in, ENCODING));
                    String line = reader.readLine();
                    // allow for update while in tx as this is READ_COMMITED
                    report("Greetings from " + txId1, line);
                    in.close();

                    rm.deleteResource(txId, "/olli/Hubert");
                    rm.deleteResource(txId, "/olli/Hubert2");
                    rm.deleteResource(txId, "/olli/Hubert3");
                    rm.deleteResource(txId, "/olli/Hubert4");
                    rm.deleteResource(txId, "/olli/Hubert5");

                    checkExactlyContains(deleteForTx(txId) + "/olli", new String[] { "Hubert", "Hubert4", "Hubert5" });

                    checkExactlyContains(changeForTx(txId) + "/olli", new String[] { "Hubert1" });

                    rm.commitTransaction(txId);
                } catch (Throwable e) {
                    System.err.println("Error: " + e);
                    e.printStackTrace();
                }
            }
        }, "Modify Thread");

        create.start();
        // be sure first thread is started before trying next
        start2Barrier.meet();
        modify.start();

        // let both transaction start before trying to shut down
        shutdownBarrier.meet();

        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        checkExactlyContains(
            STORE + "/olli",
            new String[] { "Hubert1", "Hubert6" },
            new String[] { "", "Greetings from " + txId1 + "\n" });
        checkIsEmpty(WORK);
    }

    public void testCombinedRecovery() throws Throwable {
        reset();
        createInitialFiles();
        initCommittingRecovery();
        initCommittedRecovery();
        initActiveRecovery();
        initRolledBackRecovery();
        initRollingBackRecovery();

        FileResourceManager rm =createFRM();

        // do nothing, just start and stop to check recovery of tx
        rm.start();
        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        // all but committing should be rolled back
        checkExactlyContains(STORE + "/olli", STATUS_COMMITTING_CONTEXT_RESULT_FILES);
        checkIsEmpty(WORK);
    }

    public void testCommittingRecovery() throws Throwable {
        reset();
        createInitialFiles();
        initCommittingRecovery();

        FileResourceManager rm = createFRM();

        // do nothing, just start and stop to check recovery of tx
        rm.start();
        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        checkExactlyContains(STORE + "/olli", STATUS_COMMITTING_CONTEXT_RESULT_FILES);
        checkIsEmpty(WORK);
    }

    public void testActiveRecovery() throws Throwable {
        reset();
        createInitialFiles();
        initActiveRecovery();

        FileResourceManager rm = createFRM();

        // do nothing, just start and stop to check recovery of tx
        rm.start();
        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        checkExactlyContains(STORE + "/olli", STATUS_ACTIVE_CONTEXT_RESULT_FILES);
        checkIsEmpty(WORK);
    }

    public void testRolledbackRecovery() throws Throwable {
        reset();
        createInitialFiles();
        initRolledBackRecovery();

        FileResourceManager rm = createFRM();

        // do nothing, just start and stop to check recovery of tx
        rm.start();
        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        checkExactlyContains(STORE + "/olli", STATUS_ROLLEDBACK_CONTEXT_RESULT_FILES);
        checkIsEmpty(WORK);
    }

    public void testRollingBackRecovery() throws Throwable {
        reset();
        createInitialFiles();
        initRollingBackRecovery();

        FileResourceManager rm = createFRM();

        // do nothing, just start and stop to check recovery of tx
        rm.start();
        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        checkExactlyContains(STORE + "/olli", STATUS_ROLLING_BACK_CONTEXT_RESULT_FILES);
        checkIsEmpty(WORK);
    }

    public void testCommittedRecovery() throws Throwable {
        reset();
        createInitialFiles();
        initCommittedRecovery();

        FileResourceManager rm = createFRM();

        // do nothing, just start and stop to check recovery of tx
        rm.start();
        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        checkExactlyContains(STORE + "/olli", STATUS_COMMITTED_CONTEXT_RESULT_FILES);
        checkIsEmpty(WORK);
    }

    public void testInteractiveDirtyRecovery() throws Throwable {
        reset();
        createInitialFiles();

        FileResourceManager rm = createFRM();

        rm.start();

        String txId = "DIRTY";
        rm.startTransaction(txId);
        rm.createResource(txId, "/olli/Hubert100");

        // fake a failed commit
        FileResourceManager.TransactionContext context = rm.getContext(txId);
        // needing synchronization in order not to interfer with shutdown thread
        synchronized (context) {
            logger.fine("Committing Tx " + txId);

            context.status = Status.STATUS_COMMITTING;
            context.saveState();
            rm.dirty = true;
            context.finalCleanUp();
            context.notifyFinish();
        }

        // should be allowed
        rm.readResource(txId, "/olli/Hubert");

        // should be disallowed
        boolean writeDeniedByDirty = false;
        try {
            rm.createResource(txId, "/olli/Hubert10");
        } catch (ResourceManagerSystemException rmse) {
            writeDeniedByDirty = true;
        }
        assertTrue(writeDeniedByDirty);

        // on success (expected) resets dirty flag
        rm.recover();

        // should all be allowed again
        txId = "DIRTYTEST";
        rm.startTransaction(txId);
        rm.readResource(txId, "/olli/Hubert");
        rm.createResource(txId, "/olli/Hubert10");
        rm.commitTransaction(txId);

        assertTrue(rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL, 5000));

        // tx rolled forward created "/olli/Hubert100", so it should be here as well
        checkExactlyContains(STORE + "/olli", new String[] { "Hubert", "Hubert100", "Hubert6", "Hubert10" });
        checkIsEmpty(WORK);
    }

    public void testConflict() throws Throwable {
        logger.info("Checking concurrent transaction features");

        reset();
        createInitialFiles();
       
        final FileResourceManager rm = createFRM();
       
        rm.start();

        final RendezvousBarrier restart = new RendezvousBarrier("restart",
                TIMEOUT, sLogger);

        for (int i = 0; i < 25; i++) {

            final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock" + i,
                    TIMEOUT, sLogger);

            Thread thread1 = new Thread(new Runnable() {
                public void run() {
                    try {
                        rm.startTransaction("tx1");
                        // first both threads get a lock, this one on res2
                        rm.createResource("tx1", "key2");
                        synchronized (deadlockBarrier1) {
                            deadlockBarrier1.meet();
                            deadlockBarrier1.reset();
                        }
                        // if I am first, the other thread will be dead, i.e.
                        // exactly one
                        rm.createResource("tx1", "key1");
                        rm.commitTransaction("tx1");
                    } catch (InterruptedException ie) {
                    } catch (ResourceManagerException e) {
                        assertEquals(e.getStatus(), ResourceManagerException.ERR_DEAD_LOCK);
                        deadlockCnt++;
                        try {
                            rm.rollbackTransaction("tx1");
                        } catch (ResourceManagerException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                    } finally {
                        try {
                        synchronized (restart) {
                            restart.meet();
                            restart.reset();
                        }
                        } catch (InterruptedException ie) {}

                    }
                }
            }, "Thread1");

            thread1.start();

            rm.startTransaction("tx2");
            try {
                // first both threads get a lock, this one on res2
                rm.deleteResource("tx2", "key1");
                synchronized (deadlockBarrier1) {
                    deadlockBarrier1.meet();
                    deadlockBarrier1.reset();
                }
                //          if I am first, the other thread will be dead, i.e. exactly
                // one
                rm.deleteResource("tx2", "key2");
                rm.commitTransaction("tx2");
            } catch (ResourceManagerException e) {
                assertEquals(e.getStatus(), ResourceManagerException.ERR_DEAD_LOCK);
                deadlockCnt++;
                try {
                    rm.rollbackTransaction("tx2");
                } catch (ResourceManagerException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            } finally {
                try {
                synchronized (restart) {
                    restart.meet();
                    restart.reset();
                }
                } catch (InterruptedException ie) {}

            }

            // XXX in special scenarios the current implementation might cause both
            // owners to be deadlock victims
            if (deadlockCnt != 1) {
                sLogger.logWarning("More than one thread was deadlock victim!");
            }
            assertTrue(deadlockCnt >= 1);
            deadlockCnt = 0;
        }
    }

}
TOP

Related Classes of org.apache.commons.transaction.file.FileResourceManagerTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.