/*******************************************************************************
 * Copyright (c) 1998, 2010 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/  
package org.eclipse.persistence.testing.tests.unitofwork;

import java.io.Serializable;
import java.io.StringWriter;

import java.math.BigDecimal;

import org.eclipse.persistence.descriptors.RelationalDescriptor;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DirectToFieldMapping;
import org.eclipse.persistence.testing.tests.unitofwork.ConcurrentReadOnUpdateWithEarlyTransTest;
import org.eclipse.persistence.tools.schemaframework.TableDefinition;


/**
 * <p><b>Purpose</b>: Represents the mailing address on an Employee
 * <p><b>Description</b>: Held in a private 1:1 relationship from Employee
 * @see Employee
 */
public class ConcurrentAddress implements Serializable, Cloneable {
    public BigDecimal id;
    public String street;
    public String city;
    public String province;
    public String postalCode;
    public String country;

    //USED TO DETERMINE WHICH TEST IS RUNNING
    public static int RUNNING_TEST;

    //tests
    public static final int NONE = Integer.MIN_VALUE;
    public static final int LOCK_ON_CLONE_TEST = 55;
    public static final int LOCK_ON_CLONE_DEADLOCK = 56;
    public static final int READ_ON_UPDATE_EARLY_TRANS = 57;
    public static boolean isForBackup = false;

    public ConcurrentAddress() {
        this.city = "";
        this.province = "";
        this.postalCode = "";
        this.street = "";
        this.country = "";
    }

    public Object clone() {
        ConcurrentAddress result = new ConcurrentAddress();
        result.id = this.id;
        result.city = this.city;
        result.province = this.province;
        result.postalCode = this.postalCode;
        result.street = this.street;
        result.country = this.country;
        return result;

    }

    public String getCity() {
        return city;
    }

    public String getCountry() {
        try {
            if (ConcurrentRefreshOnCloneTest.lock != null) {
                synchronized (ConcurrentRefreshOnCloneTest.lock) {
                    if (!ConcurrentRefreshOnCloneTest.waited) {
                        if (ConcurrentRefreshOnCloneTest.readerWaiting) {
                            ConcurrentRefreshOnCloneTest.lock.notifyAll();
                            Thread.yield();
                        }

                        // verify that we are not currently holding an active lock on the system.  Which would result in a deadlock
                        if (((AbstractSession)ConcurrentRefreshOnCloneTest.session).getIdentityMapAccessorInstance().getIdentityMapManager().getCacheMutex().getActiveThread() != 
                            Thread.currentThread()) {
                            ConcurrentRefreshOnCloneTest.cloneWaiting = true;
                            ConcurrentRefreshOnCloneTest.lock.wait(30000);
                            ConcurrentRefreshOnCloneTest.cloneWaiting = false;
                        } else {
                            //can't lock but let's sleep to simulate a window for refresh
                            Thread.sleep(4000);
                        }
                        ConcurrentRefreshOnCloneTest.waited = true;
                    }
                }
            }
        } catch (Exception ex) {
        }
        return country;
    }

    /**
     * Return the persistent identifier of the receiver.
     */
    public BigDecimal getId() {
        return id;
    }

    public String getPostalCode() {
        return postalCode;
    }

    public String getProvince() {
        //used in the Lock on clone test
        if ((ConcurrentAddress.RUNNING_TEST == ConcurrentAddress.LOCK_ON_CLONE_TEST) || 
            (ConcurrentAddress.RUNNING_TEST == ConcurrentAddress.LOCK_ON_CLONE_DEADLOCK)) {
            if (!isForBackup) {
                synchronized (this) {
                    isForBackup = true; //the next call to this method will be for backup
                    this.notifyAll(); // let refresh update the fields
                    try {
                        this.wait(10000); //wait for refresh to update the fields
                    } catch (InterruptedException ex) {
                    }
                }
            } else {
                isForBackup = false;
            }
        }
        return province;
    }

    public String getStreet() {
        return street;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    /**
     * Set the persistent identifier of the receiver.
     */
    public void setId(BigDecimal id) {
        try {
            if (ConcurrentAddress.RUNNING_TEST == ConcurrentAddress.READ_ON_UPDATE_EARLY_TRANS) {
                if (ConcurrentReadOnUpdateWithEarlyTransTest.threadId.get().equals("Writer")) {
                    synchronized (ConcurrentReadOnUpdateWithEarlyTransTest.readAddress) {
                        ConcurrentReadOnUpdateWithEarlyTransTest.readAddress.notify(); // wake up reader.
                        ConcurrentReadOnUpdateWithEarlyTransTest.whosWaiting = 
                                ConcurrentReadOnUpdateWithEarlyTransTest.WRITER;
                        ConcurrentReadOnUpdateWithEarlyTransTest.readAddress.wait(30000);
                    }
                }
            } else {
                if (ConcurrentReadOnInsertTest.readAddress != null) {
                    synchronized (ConcurrentReadOnInsertTest.readAddress) {
                        if (ConcurrentReadOnInsertTest.depth <= 0) {
                            ConcurrentReadOnInsertTest.readAddress.notifyAll();
                        } else {
                            --ConcurrentReadOnInsertTest.depth;
                        }
                    }

                    // make an extra buffer to ensure that the read has time to run
                    Thread.sleep(2000);
                }
            }
        } catch (Exception ex) {
        }
        this.id = id;
    }

    public void setPostalCode(String postalCode) {
        this.postalCode = postalCode;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    /**
     * Print the address city and province.
     */
    public String toString() {
        StringWriter writer = new StringWriter();

        writer.write("Address: ");
        writer.write(this.street);
        writer.write(", ");
        writer.write(this.city);
        writer.write(", ");
        writer.write(this.province);
        writer.write(", ");
        writer.write(this.country);
        return writer.toString();
    }

    public static TableDefinition tableDefinition() {
        org.eclipse.persistence.tools.schemaframework.TableDefinition tabledefinition = 
            new org.eclipse.persistence.tools.schemaframework.TableDefinition();

        // SECTION: TABLE
        tabledefinition.setName("CONCURRENT_ADDRESS");

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field = 
            new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field.setName("ADDRESS_ID");
        field.setTypeName("NUMERIC");
        field.setSize(15);
        field.setShouldAllowNull(false);
        field.setIsPrimaryKey(true);
        field.setUnique(false);
        field.setIsIdentity(true);
        tabledefinition.addField(field);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field1 = 
            new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field1.setName("P_CODE");
        field1.setTypeName("VARCHAR");
        field1.setSize(20);
        field1.setShouldAllowNull(true);
        field1.setIsPrimaryKey(false);
        field1.setUnique(false);
        field1.setIsIdentity(false);
        tabledefinition.addField(field1);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field2 = 
            new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field2.setName("CITY");
        field2.setTypeName("VARCHAR");
        field2.setSize(80);
        field2.setShouldAllowNull(true);
        field2.setIsPrimaryKey(false);
        field2.setUnique(false);
        field2.setIsIdentity(false);
        tabledefinition.addField(field2);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field3 = 
            new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field3.setName("PROVINCE");
        field3.setTypeName("VARCHAR");
        field3.setSize(80);
        field3.setShouldAllowNull(true);
        field3.setIsPrimaryKey(false);
        field3.setUnique(false);
        field3.setIsIdentity(false);
        tabledefinition.addField(field3);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field4 = 
            new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field4.setName("STREET");
        field4.setTypeName("VARCHAR");
        field4.setSize(80);
        field4.setShouldAllowNull(true);
        field4.setIsPrimaryKey(false);
        field4.setUnique(false);
        field4.setIsIdentity(false);
        tabledefinition.addField(field4);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field5 = 
            new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field5.setName("COUNTRY");
        field5.setTypeName("VARCHAR");
        field5.setSize(80);
        field5.setShouldAllowNull(true);
        field5.setIsPrimaryKey(false);
        field5.setUnique(false);
        field5.setIsIdentity(false);
        tabledefinition.addField(field5);

        return tabledefinition;
    }

    public static RelationalDescriptor descriptor() {
        // NOTE: weights are set on the mapping to ensure the order in which they are
        // accessed by toplink for timing purposes for test ConcurrentRefreshOnCloneTest
        RelationalDescriptor descriptor = new RelationalDescriptor();
        descriptor.setJavaClass(ConcurrentAddress.class);
        descriptor.addTableName("CONCURRENT_ADDRESS");
        descriptor.addPrimaryKeyFieldName("CONCURRENT_ADDRESS.ADDRESS_ID");

        // RelationalDescriptor properties.
        descriptor.useFullIdentityMap();
        descriptor.setIdentityMapSize(100);
        descriptor.setSequenceNumberFieldName("ADDRESS_ID");
        descriptor.setSequenceNumberName("ADDRESS_SEQ");

        // Query manager.
        descriptor.getQueryManager().checkCacheForDoesExist();

        // Event manager.
        // Mappings.
        DirectToFieldMapping cityMapping = new DirectToFieldMapping();
        cityMapping.setAttributeName("city");
        cityMapping.setFieldName("CONCURRENT_ADDRESS.CITY");
        cityMapping.setWeight(new Integer(10));
        descriptor.addMapping(cityMapping);

        DirectToFieldMapping countryMapping = new DirectToFieldMapping();
        countryMapping.setAttributeName("country");
        countryMapping.setFieldName("CONCURRENT_ADDRESS.COUNTRY");
        countryMapping.setSetMethodName("setCountry");
        countryMapping.setGetMethodName("getCountry");
        countryMapping.setWeight(new Integer(9));
        descriptor.addMapping(countryMapping);

        DirectToFieldMapping idMapping = new DirectToFieldMapping();
        idMapping.setAttributeName("id");
        idMapping.setFieldName("CONCURRENT_ADDRESS.ADDRESS_ID");
        idMapping.setSetMethodName("setId");
        idMapping.setGetMethodName("getId");
        idMapping.setWeight(new Integer(8));
        descriptor.addMapping(idMapping);

        DirectToFieldMapping postalCodeMapping = new DirectToFieldMapping();
        postalCodeMapping.setAttributeName("postalCode");
        postalCodeMapping.setFieldName("CONCURRENT_ADDRESS.P_CODE");
        postalCodeMapping.setWeight(new Integer(7));
        descriptor.addMapping(postalCodeMapping);

        DirectToFieldMapping provinceMapping = new DirectToFieldMapping();
        provinceMapping.setAttributeName("province");
        provinceMapping.setFieldName("CONCURRENT_ADDRESS.PROVINCE");
        provinceMapping.setSetMethodName("setProvince");
        provinceMapping.setGetMethodName("getProvince");
        provinceMapping.setWeight(new Integer(6));
        descriptor.addMapping(provinceMapping);

        DirectToFieldMapping streetMapping = new DirectToFieldMapping();
        streetMapping.setAttributeName("street");
        streetMapping.setFieldName("CONCURRENT_ADDRESS.STREET");
        streetMapping.setWeight(new Integer(5));
        descriptor.addMapping(streetMapping);

        return descriptor;
    }
}
