1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "IDBDatabaseBackendImpl.h"
28
29#if ENABLE(INDEXED_DATABASE)
30
31#include "CrossThreadTask.h"
32#include "DOMStringList.h"
33#include "IDBBackingStore.h"
34#include "IDBDatabaseException.h"
35#include "IDBFactoryBackendImpl.h"
36#include "IDBObjectStoreBackendImpl.h"
37#include "IDBTransactionBackendImpl.h"
38#include "IDBTransactionCoordinator.h"
39
40namespace WebCore {
41
42class IDBDatabaseBackendImpl::PendingSetVersionCall : public RefCounted<PendingSetVersionCall> {
43public:
44    static PassRefPtr<PendingSetVersionCall> create(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
45    {
46        return adoptRef(new PendingSetVersionCall(version, callbacks, databaseCallbacks));
47    }
48    String version() { return m_version; }
49    PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
50    PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks() { return m_databaseCallbacks; }
51
52private:
53    PendingSetVersionCall(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
54        : m_version(version)
55        , m_callbacks(callbacks)
56        , m_databaseCallbacks(databaseCallbacks)
57    {
58    }
59    String m_version;
60    RefPtr<IDBCallbacks> m_callbacks;
61    RefPtr<IDBDatabaseCallbacks> m_databaseCallbacks;
62};
63
64IDBDatabaseBackendImpl::IDBDatabaseBackendImpl(const String& name, IDBBackingStore* backingStore, IDBTransactionCoordinator* coordinator, IDBFactoryBackendImpl* factory, const String& uniqueIdentifier)
65    : m_backingStore(backingStore)
66    , m_id(InvalidId)
67    , m_name(name)
68    , m_version("")
69    , m_identifier(uniqueIdentifier)
70    , m_factory(factory)
71    , m_transactionCoordinator(coordinator)
72{
73    ASSERT(!m_name.isNull());
74
75    bool success = m_backingStore->extractIDBDatabaseMetaData(m_name, m_version, m_id);
76    ASSERT_UNUSED(success, success == (m_id != InvalidId));
77    if (!m_backingStore->setIDBDatabaseMetaData(m_name, m_version, m_id, m_id == InvalidId))
78        ASSERT_NOT_REACHED(); // FIXME: Need better error handling.
79    loadObjectStores();
80}
81
82IDBDatabaseBackendImpl::~IDBDatabaseBackendImpl()
83{
84    m_factory->removeIDBDatabaseBackend(m_identifier);
85}
86
87PassRefPtr<IDBBackingStore> IDBDatabaseBackendImpl::backingStore() const
88{
89    return m_backingStore;
90}
91
92PassRefPtr<DOMStringList> IDBDatabaseBackendImpl::objectStoreNames() const
93{
94    RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
95    for (ObjectStoreMap::const_iterator it = m_objectStores.begin(); it != m_objectStores.end(); ++it)
96        objectStoreNames->append(it->first);
97    return objectStoreNames.release();
98}
99
100PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::createObjectStore(const String& name, const String& keyPath, bool autoIncrement, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
101{
102    ASSERT(transactionPtr->mode() == IDBTransaction::VERSION_CHANGE);
103
104    if (m_objectStores.contains(name)) {
105        ec = IDBDatabaseException::CONSTRAINT_ERR;
106        return 0;
107    }
108
109    RefPtr<IDBObjectStoreBackendImpl> objectStore = IDBObjectStoreBackendImpl::create(m_backingStore.get(), m_id, name, keyPath, autoIncrement);
110    ASSERT(objectStore->name() == name);
111
112    RefPtr<IDBDatabaseBackendImpl> database = this;
113    RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
114    if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::createObjectStoreInternal, database, objectStore, transaction),
115                                   createCallbackTask(&IDBDatabaseBackendImpl::removeObjectStoreFromMap, database, objectStore))) {
116        ec = IDBDatabaseException::NOT_ALLOWED_ERR;
117        return 0;
118    }
119
120    m_objectStores.set(name, objectStore);
121    return objectStore.release();
122}
123
124void IDBDatabaseBackendImpl::createObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore,  PassRefPtr<IDBTransactionBackendInterface> transaction)
125{
126    int64_t objectStoreId;
127
128    if (!database->m_backingStore->createObjectStore(database->id(), objectStore->name(), objectStore->keyPath(), objectStore->autoIncrement(), objectStoreId)) {
129        transaction->abort();
130        return;
131    }
132
133    objectStore->setId(objectStoreId);
134    transaction->didCompleteTaskEvents();
135}
136
137PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::objectStore(const String& name)
138{
139    return m_objectStores.get(name);
140}
141
142void IDBDatabaseBackendImpl::deleteObjectStore(const String& name, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
143{
144    RefPtr<IDBObjectStoreBackendImpl> objectStore = m_objectStores.get(name);
145    if (!objectStore) {
146        ec = IDBDatabaseException::NOT_FOUND_ERR;
147        return;
148    }
149    RefPtr<IDBDatabaseBackendImpl> database = this;
150    RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
151    if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::deleteObjectStoreInternal, database, objectStore, transaction),
152                                   createCallbackTask(&IDBDatabaseBackendImpl::addObjectStoreToMap, database, objectStore))) {
153        ec = IDBDatabaseException::NOT_ALLOWED_ERR;
154        return;
155    }
156    m_objectStores.remove(name);
157}
158
159void IDBDatabaseBackendImpl::deleteObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore, PassRefPtr<IDBTransactionBackendInterface> transaction)
160{
161    database->m_backingStore->deleteObjectStore(database->id(), objectStore->id());
162    transaction->didCompleteTaskEvents();
163}
164
165void IDBDatabaseBackendImpl::setVersion(const String& version, PassRefPtr<IDBCallbacks> prpCallbacks, PassRefPtr<IDBDatabaseCallbacks> prpDatabaseCallbacks, ExceptionCode& ec)
166{
167    RefPtr<IDBCallbacks> callbacks = prpCallbacks;
168    RefPtr<IDBDatabaseCallbacks> databaseCallbacks = prpDatabaseCallbacks;
169    if (!m_databaseCallbacksSet.contains(databaseCallbacks)) {
170        callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::ABORT_ERR, "Connection was closed before set version transaction was created"));
171        return;
172    }
173    for (DatabaseCallbacksSet::const_iterator it = m_databaseCallbacksSet.begin(); it != m_databaseCallbacksSet.end(); ++it) {
174        if (*it != databaseCallbacks)
175            (*it)->onVersionChange(version);
176    }
177    if (m_databaseCallbacksSet.size() > 1) {
178        callbacks->onBlocked();
179        RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
180        m_pendingSetVersionCalls.append(pendingSetVersionCall);
181        return;
182    }
183
184    RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
185    RefPtr<IDBDatabaseBackendImpl> database = this;
186    RefPtr<IDBTransactionBackendInterface> transaction = IDBTransactionBackendImpl::create(objectStoreNames.get(), IDBTransaction::VERSION_CHANGE, this);
187    if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::setVersionInternal, database, version, callbacks, transaction),
188                                   createCallbackTask(&IDBDatabaseBackendImpl::resetVersion, database, m_version))) {
189        ec = IDBDatabaseException::NOT_ALLOWED_ERR;
190    }
191}
192
193void IDBDatabaseBackendImpl::setVersionInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBTransactionBackendInterface> transaction)
194{
195    int64_t databaseId = database->id();
196    database->m_version = version;
197    if (!database->m_backingStore->setIDBDatabaseMetaData(database->m_name, database->m_version, databaseId, databaseId == InvalidId)) {
198        // FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors.
199        callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage."));
200        transaction->abort();
201        return;
202    }
203    callbacks->onSuccess(transaction);
204}
205
206PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::transaction(DOMStringList* objectStoreNames, unsigned short mode, ExceptionCode& ec)
207{
208    for (size_t i = 0; i < objectStoreNames->length(); ++i) {
209        if (!m_objectStores.contains(objectStoreNames->item(i))) {
210            ec = IDBDatabaseException::NOT_FOUND_ERR;
211            return 0;
212        }
213    }
214
215    // FIXME: Return not allowed err if close has been called.
216    return IDBTransactionBackendImpl::create(objectStoreNames, mode, this);
217}
218
219void IDBDatabaseBackendImpl::open(PassRefPtr<IDBDatabaseCallbacks> callbacks)
220{
221    m_databaseCallbacksSet.add(RefPtr<IDBDatabaseCallbacks>(callbacks));
222}
223
224void IDBDatabaseBackendImpl::close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)
225{
226    RefPtr<IDBDatabaseCallbacks> callbacks = prpCallbacks;
227    ASSERT(m_databaseCallbacksSet.contains(callbacks));
228    m_databaseCallbacksSet.remove(callbacks);
229    if (m_databaseCallbacksSet.size() > 1)
230        return;
231
232    while (!m_pendingSetVersionCalls.isEmpty()) {
233        ExceptionCode ec = 0;
234        RefPtr<PendingSetVersionCall> pendingSetVersionCall = m_pendingSetVersionCalls.takeFirst();
235        setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
236        ASSERT(!ec);
237    }
238}
239
240void IDBDatabaseBackendImpl::loadObjectStores()
241{
242    Vector<int64_t> ids;
243    Vector<String> names;
244    Vector<String> keyPaths;
245    Vector<bool> autoIncrementFlags;
246    m_backingStore->getObjectStores(m_id, ids, names, keyPaths, autoIncrementFlags);
247
248    ASSERT(names.size() == ids.size());
249    ASSERT(keyPaths.size() == ids.size());
250    ASSERT(autoIncrementFlags.size() == ids.size());
251
252    for (size_t i = 0; i < ids.size(); i++)
253        m_objectStores.set(names[i], IDBObjectStoreBackendImpl::create(m_backingStore.get(), m_id, ids[i], names[i], keyPaths[i], autoIncrementFlags[i]));
254}
255
256void IDBDatabaseBackendImpl::removeObjectStoreFromMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
257{
258    ASSERT(database->m_objectStores.contains(objectStore->name()));
259    database->m_objectStores.remove(objectStore->name());
260}
261
262void IDBDatabaseBackendImpl::addObjectStoreToMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
263{
264    RefPtr<IDBObjectStoreBackendImpl> objectStorePtr = objectStore;
265    ASSERT(!database->m_objectStores.contains(objectStorePtr->name()));
266    database->m_objectStores.set(objectStorePtr->name(), objectStorePtr);
267}
268
269void IDBDatabaseBackendImpl::resetVersion(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version)
270{
271    database->m_version = version;
272}
273
274
275} // namespace WebCore
276
277#endif // ENABLE(INDEXED_DATABASE)
278