/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.openjpa.xmlstore;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.openjpa.meta.ClassMetaData;

/**
 * Represents a store of object data encoded in XML. This store only allows
 * one datastore transaction to proceed at a time. File I/O errors can put
 * this store into an invalid state.
 */
public class XMLStore {

    private final XMLConfiguration _conf;

    // each key in the map is a least-derived class metadata object, and each
    // value is a map of oids to object datas representing the instances of
    // that class, including subclasses
    private final Map _metaOidMaps = new HashMap();

    // store gets locked during transactions
    private boolean _locked;

    /**
     * Constructor; supply configuration.
     */
    public XMLStore(XMLConfiguration conf) {
        _conf = conf;
    }

    /**
     * Return the data for the given oid, or null if it does not exist.
     */
    public synchronized ObjectData getData(ClassMetaData meta, Object oid) {
        meta = getLeastDerived(meta);
        return (ObjectData) getMap(meta).get(oid);
    }

    /**
     * Return all datas for the base class of the given type.
     */
    public synchronized ObjectData[] getData(ClassMetaData meta) {
        meta = getLeastDerived(meta);
        Collection vals = getMap(meta).values();
        return (ObjectData[]) vals.toArray(new ObjectData[vals.size()]);
    }

    /**
     * Returns the map of oids to object datas for the given least-derived type.
     */
    private Map getMap(ClassMetaData meta) {
        Map m = (Map) _metaOidMaps.get(meta);
        if (m != null)
            return m;

        // load datas from file and cache them
        Collection datas = _conf.getFileHandler().load(meta);
        m = new HashMap(datas.size());
        for (Object o : datas) {
            ObjectData data = (ObjectData) o;
            m.put(data.getId(), data);
        }
        _metaOidMaps.put(meta, m);
        return m;
    }

    /**
     * Return the least-derived metadata in the inheritance chain
     * above <code>meta</code>, or <code>meta</code> if it is a
     * least-derived metadata.
     */
    private static ClassMetaData getLeastDerived(ClassMetaData meta) {
        while (meta.getPCSuperclass() != null)
            meta = meta.getPCSuperclassMetaData();
        return meta;
    }

    /**
     * Begin a datastore transaction. Obtains an exclusive write lock on the
     * store.
     */
    public synchronized void beginTransaction() {
        // lock store
        while (_locked)
            try {
                wait();
            } catch (InterruptedException ie) {
            }
        _locked = true;
    }

    /**
     * End the datastore transaction.
     *
     * @param updates {@link ObjectData} instances to insert or update
     * @param deletes {@link ObjectData} instances to delete
     */
    public synchronized void endTransaction(Collection<ObjectData> updates,
                                            Collection<ObjectData> deletes) {
        // track dirty types
        Set dirty = new HashSet();
        try {
            // commit updates
            if (updates != null) {
                for (ObjectData data : updates) {
                    ClassMetaData meta = getLeastDerived(data.getMetaData());
                    getMap(meta).put(data.getId(), data);
                    dirty.add(meta);
                }
            }

            // commit deletes
            if (deletes != null) {
                for (ObjectData data : deletes) {
                    ClassMetaData meta = getLeastDerived(data.getMetaData());
                    getMap(meta).remove(data.getId());
                    dirty.add(meta);
                }
            }

            // write changes to dirty extents back to file
            XMLFileHandler fh = _conf.getFileHandler();
            for (Object o : dirty) {
                ClassMetaData meta = (ClassMetaData) o;
                fh.store(meta, getMap(meta).values());
            }
        }
        finally {
            // unlock store
            notify();
            _locked = false;
        }
    }
}
