/******************************************************************************* * Copyright (c) 2011, 2015 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * William Chen (Wind River)- [345387]Open the remote files with a proper editor *******************************************************************************/ package org.eclipse.tcf.te.tcf.filesystem.core.internal.url; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.core.runtime.Assert; import org.eclipse.osgi.util.NLS; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.services.IFileSystem; import org.eclipse.tcf.services.IFileSystem.DoneClose; import org.eclipse.tcf.services.IFileSystem.DoneOpen; import org.eclipse.tcf.services.IFileSystem.FileSystemException; import org.eclipse.tcf.services.IFileSystem.IFileHandle; import org.eclipse.tcf.te.tcf.core.Tcf; import org.eclipse.tcf.te.tcf.filesystem.core.internal.FSTreeNode; import org.eclipse.tcf.te.tcf.filesystem.core.internal.exceptions.TCFChannelException; import org.eclipse.tcf.te.tcf.filesystem.core.nls.Messages; import org.eclipse.tcf.te.tcf.filesystem.core.services.Operation; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode; import org.eclipse.tcf.te.tcf.locator.interfaces.services.IPeerModelLookupService; import org.eclipse.tcf.te.tcf.locator.model.ModelManager; /** * The URL connection returned by TCF stream service used to handle "tcf" * stream protocol. */ public class TcfURLConnection extends URLConnection { // Default connecting timeout. private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // Default file opening timeout. private static final int DEFAULT_OPEN_TIMEOUT = 5000; // Default file reading timeout. private static final int DEFAULT_READ_TIMEOUT = 5000; // Default file closing timeout. private static final int DEFAULT_CLOSE_TIMEOUT = 5000; // The schema name of the stream protocol. public static final String PROTOCOL_SCHEMA = "tcf"; //$NON-NLS-1$ // The input stream of this connection. private TcfInputStream inputStream; // The output stream of this connection. private TcfOutputStream outputStream; // The TCF agent peer of the connection. private IPeer peer; // The path to the resource on the remote file system. String path; // The timeout for opening a file. private int openTimeout; // The timeout for closing a file. private int closeTimeout; // The TCF channel used to open and read the resource. IChannel channel; // The file's handle IFileHandle handle; // The file service IFileSystem service; /** * Create a TCF URL Connection using the specified url. The format of this * URL should be: tcf:/<peerName>/remote/path/to/the/resource... The * stream protocol schema is designed in this way in order to retrieve the * agent peer ID without knowing the structure of a TCF peer id. * * @see TcfURLStreamHandlerService#parseURL(URL, String, int, int) * @param url * The URL of the resource. */ public TcfURLConnection(final URL url) throws IOException { super(url); try { URI uri = url.toURI(); String peerName = uri.getAuthority(); Assert.isNotNull(peerName); peer = findPeer(peerName); if (peer == null) { throw new IOException(NLS.bind(Messages.TcfURLConnection_NoPeerFound, peerName)); } path = FSTreeNode.stripNoSlashMarker(uri.getPath()); // Set default timeout. setConnectTimeout(DEFAULT_CONNECT_TIMEOUT); setOpenTimeout(DEFAULT_OPEN_TIMEOUT); setReadTimeout(DEFAULT_READ_TIMEOUT); setCloseTimeout(DEFAULT_CLOSE_TIMEOUT); } catch (URISyntaxException e) { throw new IOException(Messages.TcfURLConnection_errorInvalidURL + url.toString(), e); } } /** * Find the TCF peer with the specified ID. * * @param peerId The target peer's ID. * @return The peer with this ID or null if not found. */ private IPeer findPeer(final String peerName) { Assert.isNotNull(peerName); final AtomicReference<IPeer> peer = new AtomicReference<IPeer>(); Runnable runnable = new Runnable() { @Override public void run() { IPeer p = Protocol.getLocator().getPeers().get(peerName); if (p == null) { IPeerNode[] peerNode = ModelManager.getPeerModel().getService(IPeerModelLookupService.class).lkupPeerModelByName(peerName); if (peerNode != null && peerNode.length > 0) p = peerNode[0].getPeer(); } peer.set(p); } }; if (Protocol.isDispatchThread()) runnable.run(); else Protocol.invokeAndWait(runnable); return peer.get(); } /** * Get the timeout for closing a file. * * @return the timeout in milliseconds. */ public long getCloseTimeout() { return closeTimeout; } /** * Set the timeout for closing a file. * * @param closeTimeout * the timeout in milliseconds. */ public void setCloseTimeout(int closeTimeout) { this.closeTimeout = closeTimeout; } /** * Get the timeout for opening a file. * * @return the timeout in milliseconds. */ public long getOpenTimeout() { return openTimeout; } /** * Set the timeout for opening a file. * * @param openTimeout * the timeout in milliseconds. */ public void setOpenTimeout(int openTimeout) { this.openTimeout = openTimeout; } /** * Open a file on the remote file system for read/write and store the file handle. * * @throws IOException Opening file fails. */ private void openFile() throws IOException { if(peer == null) throw new IOException(Messages.TcfURLConnection_NoSuchTcfAgent); try { // Open the channel channel = Operation.openChannel(peer); } catch (TCFChannelException e) { throw new IOException(e.getMessage()); } if (channel != null) { service = Operation.getBlockingFileSystem(channel); if (service != null) { final FileSystemException[] errors = new FileSystemException[1]; // Open the file. int open_flag = 0; if (doInput) open_flag |= IFileSystem.TCF_O_READ; if (doOutput) open_flag |= IFileSystem.TCF_O_WRITE | IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_TRUNC; service.open(path, open_flag, null, new DoneOpen() { @Override public void doneOpen(IToken token, FileSystemException error, IFileHandle hdl) { errors[0] = error; handle = hdl; } }); if (errors[0] != null) { IOException exception = new IOException(errors[0].toString()); exception.initCause(errors[0]); throw exception; } if (handle == null) { throw new IOException(Messages.TcfURLConnection_NoFileHandleReturned); } } else { throw new IOException(Messages.Operation_NoFileSystemError); } } } /* * (non-Javadoc) * * @see java.net.URLConnection#connect() */ @Override public void connect() throws IOException { if (!connected) { openFile(); if (doInput) { inputStream = new TcfInputStream(this); } if (doOutput) { outputStream = new TcfOutputStream(this); } connected = true; } } /* * (non-Javadoc) * * @see java.net.URLConnection#getInputStream() */ @Override public InputStream getInputStream() throws IOException { if (!connected) connect(); return inputStream; } /* * (non-Javadoc) * @see java.net.URLConnection#getOutputStream() */ @Override public OutputStream getOutputStream() throws IOException { if (!connected) connect(); return outputStream; } /** * Close the stream, release its file handler and close * the TCF channel used if possible. * * @param stream The stream either the input stream or the output stream. * @throws IOException If closing file handle times out. */ public synchronized void closeStream(Closeable stream) throws IOException { boolean shouldClose = shouldCloseFileHandle(stream); if (shouldClose) { service.close(handle, new DoneClose() { @Override public void doneClose(IToken token, FileSystemException error) { Tcf.getChannelManager().closeChannel(channel); } }); } } /** * Decide if the file handle and the TCF channel should be closed if * the specified stream is closed. If the stream is the last stream * that depends on the file handle and the TCF channel, then it should * be closed. * * @param stream The stream to be closed. * @return true if the file handle and the TCF channel should be closed. */ private boolean shouldCloseFileHandle(Closeable stream) { boolean shouldClose = false; if (stream == inputStream) { if (doOutput) { if (outputStream.closed) { shouldClose = true; } } else { shouldClose = true; } } else if (stream == outputStream) { if (doInput) { if (inputStream.closed) shouldClose = true; } else { shouldClose = true; } } return shouldClose; } }