michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.sync.middleware; michael@0: michael@0: import java.io.UnsupportedEncodingException; michael@0: import java.util.concurrent.ExecutorService; michael@0: michael@0: import org.mozilla.gecko.sync.CryptoRecord; michael@0: import org.mozilla.gecko.sync.crypto.CryptoException; michael@0: import org.mozilla.gecko.sync.crypto.KeyBundle; michael@0: import org.mozilla.gecko.sync.repositories.InactiveSessionException; michael@0: import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; michael@0: import org.mozilla.gecko.sync.repositories.RecordFactory; michael@0: import org.mozilla.gecko.sync.repositories.RepositorySession; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate; michael@0: import org.mozilla.gecko.sync.repositories.domain.Record; michael@0: michael@0: /** michael@0: * It's a RepositorySession that accepts Records as input, producing CryptoRecords michael@0: * for submission to a remote service. michael@0: * Takes a RecordFactory as a parameter. This is in charge of taking decrypted CryptoRecords michael@0: * as input and producing some expected kind of Record as output for local use. michael@0: * michael@0: * michael@0: michael@0: michael@0: michael@0: +------------------------------------+ michael@0: | Server11RepositorySession | michael@0: +-------------------------+----------+ michael@0: ^ | michael@0: | | michael@0: Encrypted CryptoRecords michael@0: | | michael@0: | v michael@0: +---------+--------------------------+ michael@0: | Crypto5MiddlewareRepositorySession | michael@0: +------------------------------------+ michael@0: ^ | michael@0: | | Decrypted CryptoRecords michael@0: | | michael@0: | +---------------+ michael@0: | | RecordFactory | michael@0: | +--+------------+ michael@0: | | michael@0: Local Record instances michael@0: | | michael@0: | v michael@0: +---------+--------------------------+ michael@0: | Local RepositorySession instance | michael@0: +------------------------------------+ michael@0: michael@0: michael@0: * @author rnewman michael@0: * michael@0: */ michael@0: public class Crypto5MiddlewareRepositorySession extends MiddlewareRepositorySession { michael@0: private KeyBundle keyBundle; michael@0: private RecordFactory recordFactory; michael@0: michael@0: public Crypto5MiddlewareRepositorySession(RepositorySession session, Crypto5MiddlewareRepository repository, RecordFactory recordFactory) { michael@0: super(session, repository); michael@0: this.keyBundle = repository.keyBundle; michael@0: this.recordFactory = recordFactory; michael@0: } michael@0: michael@0: public class DecryptingTransformingFetchDelegate implements RepositorySessionFetchRecordsDelegate { michael@0: private RepositorySessionFetchRecordsDelegate next; michael@0: private KeyBundle keyBundle; michael@0: private RecordFactory recordFactory; michael@0: michael@0: DecryptingTransformingFetchDelegate(RepositorySessionFetchRecordsDelegate next, KeyBundle bundle, RecordFactory recordFactory) { michael@0: this.next = next; michael@0: this.keyBundle = bundle; michael@0: this.recordFactory = recordFactory; michael@0: } michael@0: michael@0: @Override michael@0: public void onFetchFailed(Exception ex, Record record) { michael@0: next.onFetchFailed(ex, record); michael@0: } michael@0: michael@0: @Override michael@0: public void onFetchedRecord(Record record) { michael@0: CryptoRecord r; michael@0: try { michael@0: r = (CryptoRecord) record; michael@0: } catch (ClassCastException e) { michael@0: next.onFetchFailed(e, record); michael@0: return; michael@0: } michael@0: r.keyBundle = keyBundle; michael@0: try { michael@0: r.decrypt(); michael@0: } catch (Exception e) { michael@0: next.onFetchFailed(e, r); michael@0: return; michael@0: } michael@0: Record transformed; michael@0: try { michael@0: transformed = this.recordFactory.createRecord(r); michael@0: } catch (Exception e) { michael@0: next.onFetchFailed(e, r); michael@0: return; michael@0: } michael@0: next.onFetchedRecord(transformed); michael@0: } michael@0: michael@0: @Override michael@0: public void onFetchCompleted(final long fetchEnd) { michael@0: next.onFetchCompleted(fetchEnd); michael@0: } michael@0: michael@0: @Override michael@0: public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) { michael@0: // Synchronously perform *our* work, passing through appropriately. michael@0: RepositorySessionFetchRecordsDelegate deferredNext = next.deferredFetchDelegate(executor); michael@0: return new DecryptingTransformingFetchDelegate(deferredNext, keyBundle, recordFactory); michael@0: } michael@0: } michael@0: michael@0: private DecryptingTransformingFetchDelegate makeUnwrappingDelegate(RepositorySessionFetchRecordsDelegate inner) { michael@0: if (inner == null) { michael@0: throw new IllegalArgumentException("Inner delegate cannot be null!"); michael@0: } michael@0: return new DecryptingTransformingFetchDelegate(inner, this.keyBundle, this.recordFactory); michael@0: } michael@0: michael@0: @Override michael@0: public void fetchSince(long timestamp, michael@0: RepositorySessionFetchRecordsDelegate delegate) { michael@0: inner.fetchSince(timestamp, makeUnwrappingDelegate(delegate)); michael@0: } michael@0: michael@0: @Override michael@0: public void fetch(String[] guids, michael@0: RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException { michael@0: inner.fetch(guids, makeUnwrappingDelegate(delegate)); michael@0: } michael@0: michael@0: @Override michael@0: public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) { michael@0: inner.fetchAll(makeUnwrappingDelegate(delegate)); michael@0: } michael@0: michael@0: @Override michael@0: public void setStoreDelegate(RepositorySessionStoreDelegate delegate) { michael@0: // TODO: it remains to be seen how this will work. michael@0: inner.setStoreDelegate(delegate); michael@0: this.delegate = delegate; // So we can handle errors without involving inner. michael@0: } michael@0: michael@0: @Override michael@0: public void store(Record record) throws NoStoreDelegateException { michael@0: if (delegate == null) { michael@0: throw new NoStoreDelegateException(); michael@0: } michael@0: CryptoRecord rec = record.getEnvelope(); michael@0: rec.keyBundle = this.keyBundle; michael@0: try { michael@0: rec.encrypt(); michael@0: } catch (UnsupportedEncodingException e) { michael@0: delegate.onRecordStoreFailed(e, record.guid); michael@0: return; michael@0: } catch (CryptoException e) { michael@0: delegate.onRecordStoreFailed(e, record.guid); michael@0: return; michael@0: } michael@0: // Allow the inner session to do delegate handling. michael@0: inner.store(rec); michael@0: } michael@0: }