/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* 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.jumpmind.symmetric.transport.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
import org.jumpmind.symmetric.AbstractSymmetricEngine;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.io.IoConstants;
import org.jumpmind.symmetric.model.BatchAck;
import org.jumpmind.symmetric.model.ChannelMap;
import org.jumpmind.symmetric.model.IncomingBatch;
import org.jumpmind.symmetric.model.Node;
import org.jumpmind.symmetric.model.OutgoingBatch;
import org.jumpmind.symmetric.model.ProcessInfo;
import org.jumpmind.symmetric.model.ProcessInfoKey;
import org.jumpmind.symmetric.model.ProcessType;
import org.jumpmind.symmetric.model.ProcessInfo.Status;
import org.jumpmind.symmetric.transport.AbstractTransportManager;
import org.jumpmind.symmetric.transport.IIncomingTransport;
import org.jumpmind.symmetric.transport.IOutgoingTransport;
import org.jumpmind.symmetric.transport.IOutgoingWithResponseTransport;
import org.jumpmind.symmetric.transport.ITransportManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Coordinates interaction between two symmetric engines in the same JVM.
*/
public class InternalTransportManager extends AbstractTransportManager implements ITransportManager {
static final Logger log = LoggerFactory.getLogger(InternalTransportManager.class);
protected ISymmetricEngine symmetricEngine;
public InternalTransportManager(ISymmetricEngine engine) {
super(engine.getExtensionService());
this.symmetricEngine = engine;
}
public IIncomingTransport getFilePullTransport(Node remote, final Node local, String securityToken,
Map<String, String> requestProperties, String registrationUrl) throws IOException {
final PipedOutputStream respOs = new PipedOutputStream();
final PipedInputStream respIs = new PipedInputStream(respOs);
runAtClient(remote.getSyncUrl(), null, respOs, new IClientRunnable() {
public void run(ISymmetricEngine engine, InputStream is, OutputStream os) throws Exception {
IOutgoingTransport transport = new InternalOutgoingTransport(respOs, null);
ProcessInfo processInfo = engine.getStatisticManager().newProcessInfo(
new ProcessInfoKey(engine.getNodeService().findIdentityNodeId(), local.getNodeId(),
ProcessType.FILE_SYNC_PULL_HANDLER));
try {
engine.getFileSyncService().sendFiles(processInfo, local, transport);
processInfo.setStatus(Status.OK);
} catch (RuntimeException ex) {
processInfo.setStatus(Status.ERROR);
throw ex;
}
transport.close();
}
});
return new InternalIncomingTransport(respIs);
}
public IIncomingTransport getPullTransport(Node remote, final Node local, String securityToken, Map<String, String> requestProperties,
String registrationUrl) throws IOException {
final PipedOutputStream respOs = new PipedOutputStream();
final PipedInputStream respIs = new PipedInputStream(respOs);
final ChannelMap suspendIgnoreChannels = symmetricEngine.getConfigurationService().getSuspendIgnoreChannelLists(remote.getNodeId());
runAtClient(remote.getSyncUrl(), null, respOs, new IClientRunnable() {
public void run(ISymmetricEngine engine, InputStream is, OutputStream os) throws Exception {
IOutgoingTransport transport = new InternalOutgoingTransport(respOs, suspendIgnoreChannels, IoConstants.ENCODING);
ProcessInfo processInfo = engine.getStatisticManager().newProcessInfo(
new ProcessInfoKey(engine.getNodeService().findIdentityNodeId(), local.getNodeId(), ProcessType.PULL_HANDLER));
try {
engine.getDataExtractorService().extract(processInfo, local, transport);
processInfo.setStatus(Status.OK);
} catch (RuntimeException ex) {
processInfo.setStatus(Status.ERROR);
throw ex;
}
transport.close();
}
});
return new InternalIncomingTransport(respIs);
}
public IOutgoingWithResponseTransport getPushTransport(final Node targetNode, final Node sourceNode, String securityToken,
String channelId, String registrationUrl) throws IOException {
final PipedOutputStream pushOs = new PipedOutputStream();
final PipedInputStream pushIs = new PipedInputStream(pushOs);
final PipedOutputStream respOs = new PipedOutputStream();
final PipedInputStream respIs = new PipedInputStream(respOs);
runAtClient(targetNode.getSyncUrl(), pushIs, respOs, new IClientRunnable() {
public void run(ISymmetricEngine engine, InputStream is, OutputStream os) throws Exception {
// This should be basically what the push servlet does ...
engine.getDataLoaderService().loadDataFromPush(sourceNode, pushIs, respOs);
}
});
return new InternalOutgoingWithResponseTransport(pushOs, respIs);
}
public IOutgoingWithResponseTransport getFilePushTransport(final Node targetNode, final Node sourceNode, String securityToken,
String registrationUrl) throws IOException {
final PipedOutputStream pushOs = new PipedOutputStream();
final PipedInputStream pushIs = new PipedInputStream(pushOs);
final PipedOutputStream respOs = new PipedOutputStream();
final PipedInputStream respIs = new PipedInputStream(respOs);
runAtClient(targetNode.getSyncUrl(), pushIs, respOs, new IClientRunnable() {
public void run(ISymmetricEngine engine, InputStream is, OutputStream os) throws Exception {
// This should be basically what the push servlet does ...
engine.getFileSyncService().loadFilesFromPush(sourceNode.getNodeId(), is, os);
}
});
return new InternalOutgoingWithResponseTransport(pushOs, respIs);
}
public IIncomingTransport getRegisterTransport(final Node client, String registrationUrl) throws IOException {
final PipedOutputStream respOs = new PipedOutputStream();
final PipedInputStream respIs = new PipedInputStream(respOs);
runAtClient(registrationUrl, null, respOs, new IClientRunnable() {
public void run(ISymmetricEngine engine, InputStream is, OutputStream os) throws Exception {
// This should be basically what the registration servlet does
// ...
engine.getRegistrationService().registerNode(client, os, false);
}
});
return new InternalIncomingTransport(respIs);
}
@Override
public int sendCopyRequest(Node local) throws IOException {
return -1;
}
public int sendAcknowledgement(Node remote, List<IncomingBatch> list, Node local, String securityToken, String registrationUrl)
throws IOException {
try {
if (list != null && list.size() > 0) {
ISymmetricEngine remoteEngine = getTargetEngine(remote.getSyncUrl());
String ackData = getAcknowledgementData(remote.requires13Compatiblity(), local.getNodeId(), list);
List<BatchAck> batches = readAcknowledgement(ackData);
for (BatchAck batchInfo : batches) {
remoteEngine.getAcknowledgeService().ack(batchInfo);
}
}
return HttpURLConnection.HTTP_OK;
} catch (Exception ex) {
log.error("", ex);
return -1;
}
}
public void writeAcknowledgement(OutputStream out, Node remote, List<IncomingBatch> list, Node local, String securityToken)
throws IOException {
PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, IoConstants.ENCODING), true);
pw.println(getAcknowledgementData(remote.requires13Compatiblity(), local.getNodeId(), list));
pw.close();
}
private void runAtClient(final String url, final InputStream is, final OutputStream os, final IClientRunnable runnable) {
new Thread() {
public void run() {
try {
ISymmetricEngine engine = getTargetEngine(url);
runnable.run(engine, is, os);
} catch (Exception e) {
log.error("", e);
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
}
}.start();
}
private ISymmetricEngine getTargetEngine(String url) {
ISymmetricEngine engine = AbstractSymmetricEngine.findEngineByUrl(url);
if (engine == null) {
throw new NullPointerException("Could not find the engine reference for the following url: " + url);
} else {
return engine;
}
}
@Override
public IIncomingTransport getAckStatusTransport(OutgoingBatch batch, Node remote, Node local, String securityToken, String registrationUrl) {
throw new NotImplementedException();
}
@Override
public void makeReservationTransport(String poolId, String channelId, Node remote, Node local, String securityToken,
String registrationUrl) {
}
interface IClientRunnable {
public void run(ISymmetricEngine engine, InputStream is, OutputStream os) throws Exception;
}
}