/* * RapidMiner * * Copyright (C) 2001-2014 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.repository.remote; import java.awt.Desktop; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.net.Authenticator; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.PasswordAuthentication; import java.net.ProtocolException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.net.UnknownHostException; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import javax.swing.Action; import javax.swing.event.EventListenerList; import javax.xml.namespace.QName; import javax.xml.ws.BindingProvider; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import com.rapid_i.repository.wsimport.RAInfoService; import com.rapid_i.repository.wsimport.RAInfoService_Service; import com.rapid_i.repository.wsimport.RepositoryService; import com.rapid_i.repository.wsimport.RepositoryService_Service; import com.rapidminer.gui.actions.BrowseAction; import com.rapidminer.gui.tools.PasswordDialog; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.io.Base64; import com.rapidminer.io.process.XMLTools; import com.rapidminer.repository.Entry; import com.rapidminer.repository.Folder; import com.rapidminer.repository.Repository; import com.rapidminer.repository.RepositoryException; import com.rapidminer.repository.RepositoryListener; import com.rapidminer.repository.RepositoryLocation; import com.rapidminer.repository.RepositoryManager; import com.rapidminer.repository.gui.RemoteRepositoryPanel; import com.rapidminer.repository.gui.RepositoryConfigurationPanel; import com.rapidminer.tools.GlobalAuthenticator; import com.rapidminer.tools.I18N; import com.rapidminer.tools.LogService; import com.rapidminer.tools.PasswortInputCanceledException; import com.rapidminer.tools.WebServiceTools; import com.rapidminer.tools.XMLException; import com.rapidminer.tools.cipher.CipherException; import com.rapidminer.tools.jdbc.connection.DatabaseConnectionService; import com.rapidminer.tools.jdbc.connection.FieldConnectionEntry; /** * A repository connecting to a RapidAnalytics installation. * * @author Simon Fischer, Nils Woehler */ public class RemoteRepository extends RemoteFolder implements Repository { private static final int CHECK_CONFIG_TIMEOUT = 5000; private static CountDownLatch checkConfigCountDownLatch; /** Type of object requested from a server.*/ public static enum EntryStreamType { METADATA, IOOBJECT, PROCESS, BLOB } private URL baseUrl; private String alias; private String username; private char[] password; private RepositoryService repositoryService; private ProcessServiceFacade processServiceFacade; private RAInfoService raInfoService; private final EventListenerList listeners = new EventListenerList(); private static final Map<URI, WeakReference<RemoteRepository>> ALL_REPOSITORIES = new HashMap<URI, WeakReference<RemoteRepository>>(); private static final Object MAP_LOCK = new Object(); private boolean offline = true; private boolean isHome; private boolean passwortInputCanceled = false; static { GlobalAuthenticator.registerServerAuthenticator(new GlobalAuthenticator.URLAuthenticator() { @Override public PasswordAuthentication getAuthentication(URL url) throws PasswortInputCanceledException { WeakReference<RemoteRepository> reposRef = null;// = ALL_REPOSITORIES.get(url); for (Map.Entry<URI, WeakReference<RemoteRepository>> entry : ALL_REPOSITORIES.entrySet()) { if (url.toString().startsWith(entry.getKey().toString()) || url.toString().replace("127\\.0\\.0\\.1", "localhost").startsWith(entry.getKey().toString())) { reposRef = entry.getValue(); break; } } if (reposRef == null) { return null; } RemoteRepository repository = reposRef.get(); if (repository != null) { return repository.getAuthentication(); } else { return null; } } @Override public String getName() { return "Repository authenticator"; } @Override public String toString() { return getName(); } }); } public RemoteRepository(URL baseUrl, String alias, String username, char[] password, boolean isHome) { super("/"); setRepository(this); this.setAlias(alias); this.baseUrl = baseUrl; this.setUsername(username); this.isHome = isHome; if ((password != null) && (password.length > 0)) { this.setPassword(password); } else { this.setPassword(null); } register(this); // The line below will cause a stack overflow //refreshProcessExecutionQueueNames(); } private static void register(RemoteRepository remoteRepository) { synchronized (MAP_LOCK) { try { ALL_REPOSITORIES.put(remoteRepository.getBaseUrl().toURI(), new WeakReference<RemoteRepository>(remoteRepository)); } catch (URISyntaxException e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.repository.remote.RemoteRepository.adding_repository_uri_error", remoteRepository.getBaseUrl().toExternalForm()), e); } } } /** * Checks if the provided configuration works. If it is working, <code>null</code> will be returned. * If it is not working, an error message will be returned. */ public static synchronized String checkConfiguration(String url, String username, char[] password) { URL repositoryServiceURL; HttpURLConnection conn = null; try { if ((username != null) && (username.length() != 0) && (password != null) && (password.length != 0)) { // create new count down latch with counter of 1 // because this method is synchronized, it is possible to have only // one count down latch with a count of 1 at the same time. // This will block all instances of RemoteRepository from creating webservice calls/HttpConnections // with RapidAnalytics until this method has finished. // This has to be done because we will exchange the default authenticator soon. checkConfigCountDownLatch = new CountDownLatch(1); repositoryServiceURL = getRepositoryServiceWSDLUrl(new URL(url)); // clear auth cache WebServiceTools.clearAuthCache(); // Set default authenticator to null. // This is actually pretty evil but we have to do it in order to get no PasswordDialogs when authentication fails. Authenticator.setDefault(null); // create connection conn = (HttpURLConnection) repositoryServiceURL.openConnection(); // set timeout to check config timeout conn.setReadTimeout(CHECK_CONFIG_TIMEOUT); conn.setConnectTimeout(CHECK_CONFIG_TIMEOUT); conn.setRequestProperty("Accept-Charset", "UTF-8"); // set basic auth String userpass = username + ":" + new String(password); String basicAuth = "Basic " + new String(Base64.encodeBytes(userpass.getBytes())); conn.setRequestProperty("Authorization", basicAuth); // get response code int responseCode = conn.getResponseCode(); if (200 == responseCode) { return null; // works fine, return null } // something is wrong, return according error message if (responseCode >= 400) { if (responseCode == 401) { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.authentication_error"); } else { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.other_authentication_error"); } } else if (responseCode >= 500 && responseCode < 600) { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.internal_server_error"); } else { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.unkown_error"); } } else { // username or password not set if (username == null || username.length() == 0) { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.no_user"); } else { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.no_password"); } } } catch (Throwable t) { if (t instanceof UnknownHostException) { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.unknown_host", t.getLocalizedMessage()); } if (t instanceof ConnectException) { return I18N.getMessage(I18N.getErrorBundle(), "repository.error.check_connection.connect_error", url); } return t.getMessage(); } finally { // don't forget to set the instance of GlobalAuthenticator as default authenticator again Authenticator.setDefault(GlobalAuthenticator.getInstance()); // disconnect connection if (conn != null) { conn.disconnect(); } // count down the current count down latch so it reaches zero. All instances that have waited for // checkConfiguration are now able to proceed. if(checkConfigCountDownLatch != null) { checkConfigCountDownLatch.countDown(); } } } public URL getRepositoryServiceBaseUrl() { try { return new URL(getBaseUrl(), "RAWS/"); } catch (MalformedURLException e) { // cannot happen LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.repository.remote.RemoteRepository.creating_webservice_error", e), e); return null; } } private static URL getRepositoryServiceWSDLUrl(URL baseURL) { String url = "RAWS/RepositoryService?wsdl"; try { return new URL(baseURL, url); } catch (MalformedURLException e) { // cannot happen LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.repository.remote.RemoteRepository.creating_webservice_error", url), e); return null; } } private URL getRepositoryServiceWSDLUrl() { return getRepositoryServiceWSDLUrl(getBaseUrl()); } private URL getRAInfoServiceWSDLUrl() { String url = "RAWS/RAInfoService?wsdl"; try { return new URL(getBaseUrl(), url); } catch (MalformedURLException e) { // cannot happen LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.repository.remote.RemoteRepository.creating_webservice_error", url), e); return null; } } @Override public void addRepositoryListener(RepositoryListener l) { listeners.add(RepositoryListener.class, l); } @Override public void removeRepositoryListener(RepositoryListener l) { listeners.remove(RepositoryListener.class, l); } @Override public boolean rename(String newName) { this.setAlias(newName); fireEntryChanged(this); return true; } protected void fireEntryChanged(Entry entry) { for (RepositoryListener l : listeners.getListeners(RepositoryListener.class)) { l.entryChanged(entry); } } protected void fireEntryAdded(Entry newEntry, Folder parent) { for (RepositoryListener l : listeners.getListeners(RepositoryListener.class)) { l.entryAdded(newEntry, parent); } } protected void fireEntryRemoved(Entry removedEntry, Folder parent, int index) { for (RepositoryListener l : listeners.getListeners(RepositoryListener.class)) { l.entryRemoved(removedEntry, parent, index); } } protected void fireRefreshed(Folder folder) { for (RepositoryListener l : listeners.getListeners(RepositoryListener.class)) { l.folderRefreshed(folder); } } private Map<String, RemoteEntry> cachedEntries = new HashMap<String, RemoteEntry>(); /** Connection entries fetched from server. */ private Collection<FieldConnectionEntry> connectionEntries; private boolean cachedPasswordUsed = false; /** Process queue names fetched from server */ private List<String> processExecutionQueueNames; private int protocollExceptionCount; private ProtocolException protocolException; protected void register(RemoteEntry entry) { cachedEntries.put(entry.getPath(), entry); } @Override public Entry locate(String string) throws RepositoryException { // check if connection is okay. If user has canceled the password dialog, return null if (!checkConnection()) { return null; } return RepositoryManager.getInstance(null).locate(this, string, false); } @Override public String getName() { return getAlias(); } @Override public String getState() { return (isOffline() ? "offline" : (repositoryService != null ? "connected" : "disconnected")); } @Override public String getIconName() { return I18N.getMessage(I18N.getGUIBundle(), "gui.repository.remote.icon"); } /** Returns a short HTML description of this repository. Does not include surrounding <html> tags. */ public String toHtmlString() { return getAlias() + "<br/><small style=\"color:gray\">(" + getBaseUrl() + ")</small>"; } /** This function will check if it is possible to connect to RapidAnalytics. If the user is not yet logged in, a password dialog will be shown. * If the user has canceled the password dialog <code>false</code> will be returned. The function calling this method should just return then without throwing an error itself. * If the connection can be established <code>true</code> will be returned. If there was a problem connecting to RapidAnalytics a {@link IOException} is thrown. */ protected synchronized boolean checkConnectionWithIOExpcetion() throws IOException { // even if the method is synchronized already, // we still have to check if checkConfiguration is currently running if (checkConfigCountDownLatch != null) { try { checkConfigCountDownLatch.await(); // will wait if checkConfiguration is running } catch (InterruptedException e) { // do nothing } } boolean checkingConnection = true; boolean passwortInputNotCanceled = !isPasswordInputCanceled(); InputStream inputStream = null; // Check if WSDL is reachable while (checkingConnection && passwortInputNotCanceled) { try { // this line will throw exceptions if the WSDL cannot be received byte[] temp = new byte[1]; inputStream = WebServiceTools.openStreamFromURL(getRepositoryServiceWSDLUrl(), 3000); inputStream.read(temp); // this line can only be reached if the WSDL can be reached. This is only the case if the user is logged in. checkingConnection = false; } catch (ProtocolException e) { setOffline(true); // protocol exception means that probably the username and/or the password were wrong. Reset password and show the authentication dialog again setProtocollExceptionCount(getProtocollExceptionCount() + 1); protocolException = e; setPassword(null); } catch (IOException e) { setOffline(true); // only throw a repository exception if the user has not canceled the password input if (!isPasswordInputCanceled()) { throw e; } } finally { if (inputStream != null) { inputStream.close(); inputStream = null; } } passwortInputNotCanceled = !isPasswordInputCanceled(); } setProtocollExceptionCount(0); return passwortInputNotCanceled; } /** This function will check if it is possible to connect to RapidAnalytics. If the user is not yet logged in, a password dialog will be shown. * If the user has canceled the password dialog <code>false</code> will be returned. The function calling this method should just return then without throwing an error itself. * If the connection can be established <code>true</code> will be returned. If there was a problem connecting to RapidAnalytics a RepositoryException is thrown. */ protected synchronized boolean checkConnection() throws RepositoryException { try { return checkConnectionWithIOExpcetion(); } catch (ConnectException e) { throw new RepositoryException(I18N.getMessage(I18N.getErrorBundle(), "repository.connection_exception", getName(), getBaseUrl()), e); } catch (IOException e) { // only throw a repository exception if the user has not canceled the password input if (!isPasswordInputCanceled()) { throw new RepositoryException(I18N.getMessage(I18N.getErrorBundle(), "repository.cannot_be_reached", getName()), e); } return false; } } private PasswordAuthentication getAuthentication() throws PasswortInputCanceledException { if (password == null) { LogService.getRoot().log(Level.INFO, "com.rapidminer.tools.repository.remote.RemoteRepository.authentication_requested", getBaseUrl()); PasswordAuthentication passwordAuthentication; try { passwordAuthentication = getPasswordAuthentication(); if (passwordAuthentication != null && passwordAuthentication.getUserName() != null && passwordAuthentication.getUserName().length() != 0 && passwordAuthentication.getPassword() != null && passwordAuthentication.getPassword().length != 0) { this.setPassword(passwordAuthentication.getPassword()); this.setUsername(passwordAuthentication.getUserName()); RepositoryManager.getInstance(null).save(); } setPasswortInputCanceled(false); return passwordAuthentication; } catch (PasswortInputCanceledException e) { setPasswortInputCanceled(true); setPassword(null); setOffline(true); throw e; } } else { return new PasswordAuthentication(getUsername(), password); } } /** * @return * @throws PasswortInputCanceledException */ private PasswordAuthentication getPasswordAuthentication() throws PasswortInputCanceledException { PasswordAuthentication passwordAuthentication; if (cachedPasswordUsed) { // if we have used a cached password last time, and we enter this method again, // this is probably because the password was wrong, so rather force dialog than // using cache again. if (getProtocollExceptionCount() > 3) { passwordAuthentication = PasswordDialog.getPasswordAuthentication(getAlias(), getBaseUrl().toString(), false, false, "authentication.ra.wrong.credentials.protocol.error", getName(), protocolException.getLocalizedMessage()); } else if (getProtocollExceptionCount() > 0) { passwordAuthentication = PasswordDialog.getPasswordAuthentication(getAlias(), getBaseUrl().toString(), false, false, "authentication.ra.wrong.credentials", getName()); } else { passwordAuthentication = PasswordDialog.getPasswordAuthentication(getAlias(), getBaseUrl().toString(), false, false, "authentication.ra", getName()); } this.cachedPasswordUsed = false; } else { if (getProtocollExceptionCount() > 3) { passwordAuthentication = PasswordDialog.getPasswordAuthentication(getAlias(), getBaseUrl().toString(), false, false, "authentication.ra.wrong.credentials.protocol.error", getName(), protocolException.getLocalizedMessage()); } else if (getProtocollExceptionCount() > 0) { passwordAuthentication = PasswordDialog.getPasswordAuthentication(getAlias(), getBaseUrl().toString(), false, false, "authentication.ra.wrong.credentials", getName()); } else { passwordAuthentication = PasswordDialog.getPasswordAuthentication(getAlias(), getBaseUrl().toString(), false, true, "authentication.ra", getName()); } this.cachedPasswordUsed = true; } return passwordAuthentication; } /** * @return the repository service. May return <code>null</code> if connection was refused. */ public RepositoryService getRepositoryService() throws RepositoryException { // check if connection is okay. If user has canceled the password dialog, just return if (!checkConnection()) { return null; } installJDBCConnectionEntries(); if (repositoryService == null) { try { RepositoryService_Service serviceService = new RepositoryService_Service(getRepositoryServiceWSDLUrl(), new QName("http://service.web.rapidanalytics.de/", "RepositoryService")); repositoryService = serviceService.getRepositoryServicePort(); setupBindingProvider((BindingProvider) repositoryService); setOffline(false); } catch (Exception e) { setOffline(true); setPassword(null); repositoryService = null; throw new RepositoryException("Cannot connect to " + getBaseUrl() + ": " + e, e); } } return repositoryService; } public void resetRepositoryService() throws RepositoryException { repositoryService = null; getRepositoryService(); } private void setupBindingProvider(BindingProvider bp) { WebServiceTools.setCredentials(bp, getUsername(), password); WebServiceTools.setTimeout(bp); } /** * * @return can return <code>null</code> if connection was refused */ public ProcessServiceFacade getProcessService() throws RepositoryException { // check if connection is okay. If user has canceled the password dialog, just return if (!checkConnection()) { return null; } if (processServiceFacade == null) { try { RAInfoService raInfoService = getRAInfoService(); // throw error if user has canceled passwort input if (raInfoService == null && isPasswordInputCanceled()) { throw new RepositoryException(I18N.getMessage(I18N.getErrorBundle(), "repository.login_canceled", getName())); } processServiceFacade = new ProcessServiceFacade(raInfoService, getBaseUrl(), getUsername(), password); setPasswortInputCanceled(false); setOffline(false); } catch (Exception e) { setOffline(true); setPassword(null); processServiceFacade = null; throw new RepositoryException("Cannot connect to " + getBaseUrl() + ": " + e, e); } } return processServiceFacade; } /** * @return the {@link RAInfoService} if it can be accessed. If the queried RA cannot be reached or has no RAInfoService <code>null</code> is returned. */ public RAInfoService getRAInfoService() { if (raInfoService == null) { try { checkConnection(); RAInfoService_Service serviceService = new RAInfoService_Service(getRAInfoServiceWSDLUrl(), new QName("http://service.web.rapidanalytics.de/", "RAInfoService")); //TODO how to set the namespace uri? raInfoService = serviceService.getRAInfoServicePort(); setupBindingProvider((BindingProvider) raInfoService); setPasswortInputCanceled(false); setOffline(false); } catch (Exception e) { LogService.getRoot().log(Level.INFO, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.repository.remote.RemoteRepository.cannot_fetch_info_service"), e); raInfoService = null; } } return raInfoService; } @Override public String getDescription() { return "RapidAnalytics repository at " + getBaseUrl(); } @Override public void refresh() throws RepositoryException { setPasswortInputCanceled(false); setProtocollExceptionCount(0); // check if connection is okay. If user has canceled the password dialog, just return if (!checkConnection()) { return; } cachedEntries.clear(); super.refresh(); if (!isPasswordInputCanceled()) { removeJDBCConnectionEntries(); installJDBCConnectionEntries(); refreshProcessExecutionQueueNames(); } } private void refreshProcessExecutionQueueNames() { try { refreshProcessQueueNames(getProcessService()); } catch (RepositoryException e) { processExecutionQueueNames = null; } } private void refreshProcessQueueNames(ProcessServiceFacade processService) { if (processService.getProcessServiceVersion().isAtLeast(ProcessServiceFacade.VERSION_1_3)) { processExecutionQueueNames = processService.getQueueNames(); } else { processExecutionQueueNames = null; } } /** * @return a copy of all process execution queue names. If it is <code>null</code> process queue names are not supported. */ public List<String> getProcessQueueNames() { if (processExecutionQueueNames == null) { try { ProcessServiceFacade processService = getProcessService(); refreshProcessQueueNames(processService); } catch (RepositoryException e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.repository.remote.RemoteRepository.error_fetching_process_queue_names"), e); } } return processExecutionQueueNames == null ? null : new LinkedList<String>(processExecutionQueueNames); } /** Returns a connection to a given location in the repository. * @param preAuthHeader If set, the Authorization: header will be set to basic auth. Otherwise, the {@link GlobalAuthenticator} mechanism * will be used. * @param type can be null*/ public HttpURLConnection getResourceHTTPConnection(String location, EntryStreamType type, boolean preAuthHeader) throws IOException { String split[] = location.split("/"); StringBuilder encoded = new StringBuilder(); encoded.append("RAWS/resources"); for (String fraction : split) { if (!fraction.isEmpty()) { // only for non empty to prevent double // encoded.append('/'); // Do not encode the fraction of the location. This will be done in the #getHTTPConnection() method. // Furthermore URLEncoder is the wrong class to encode URLs. It is only used to encode URL parameters. encoded.append(fraction); //encoded.append(URLEncoder.encode(fraction, "UTF-8")); } } String query = null; if (type == EntryStreamType.METADATA) { query = "?format=" + URLEncoder.encode("binmeta", "UTF-8"); } return getHTTPConnection(encoded.toString(), query, preAuthHeader); } /** * Use this function only if there are no query parameters. Use {@link #getHTTPConnection(String, String, boolean)} otherwise. * * @param pathInfo should look like 'RAWS/...' without a '/' in front. Furthermore the pathInfo should NOT be encoded. This will be done by this function. */ public HttpURLConnection getHTTPConnection(String pathInfo, boolean preAuthHeader) throws IOException { return getHTTPConnection(pathInfo, null, preAuthHeader); } /** * @param pathInfo should look like 'RAWS/...' without a '/' in front. Furthermore the pathInfo should NOT be encoded. This will be done by this function. * @param query should look like this '?format=PARAM1'. The query parameters should be encoded with URLEncoder before passing them to this function <br/>(e.g. String query = "?format="+URLEncoder.encode("binmeta", "UTF-8");). * * @return can return <code>null</code> if user cancels the password dialog */ public HttpURLConnection getHTTPConnection(String pathInfo, String query, boolean preAuthHeader) throws IOException { pathInfo = RepositoryLocation.SEPARATOR + pathInfo; URI uri; try { uri = new URI(getBaseUrl().getProtocol(), null, getBaseUrl().getHost(), getBaseUrl().getPort(), pathInfo, null, null); } catch (URISyntaxException e) { throw new IOException("Cannot connect to " + getBaseUrl() + RepositoryLocation.SEPARATOR + pathInfo + ". Location is malformed!", e); } URL url; if (query != null) { url = new URL(uri.toASCIIString() + query); } else { url = new URL(uri.toASCIIString()); } if (!preAuthHeader) { if (!checkConnectionWithIOExpcetion()) { return null; } } final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); WebServiceTools.setURLConnectionDefaults(conn); conn.setRequestProperty("Accept-Charset", "UTF-8"); if (preAuthHeader && (username != null) && (password != null)) { String userpass = username + ":" + new String(password); String basicAuth = "Basic " + new String(Base64.encodeBytes(userpass.getBytes())); conn.setRequestProperty("Authorization", basicAuth); } return conn; } @Override public Element createXML(Document doc) { Element repositoryElement = doc.createElement("remoteRepository"); Element url = doc.createElement("url"); url.appendChild(doc.createTextNode(this.getBaseUrl().toString())); repositoryElement.appendChild(url); Element alias = doc.createElement("alias"); alias.appendChild(doc.createTextNode(this.getAlias())); repositoryElement.appendChild(alias); Element user = doc.createElement("user"); user.appendChild(doc.createTextNode(this.getUsername())); repositoryElement.appendChild(user); return repositoryElement; } public static RemoteRepository fromXML(Element element) throws XMLException { String url = XMLTools.getTagContents(element, "url", true); try { return new RemoteRepository(new URL(url), XMLTools.getTagContents(element, "alias", true), XMLTools.getTagContents(element, "user", true), null, false); } catch (MalformedURLException e) { throw new XMLException("Illegal url '" + url + "': " + e, e); } } @Override public void delete() { RepositoryManager.getInstance(null).removeRepository(this); } /** * @deprecated do not use, as it returns wrong results in case repositories are removed. * Use {@link RepositoryManager#getRemoteRepositories()} instead. */ @Deprecated public static List<RemoteRepository> getAll() { List<RemoteRepository> result = new LinkedList<RemoteRepository>(); for (WeakReference<RemoteRepository> ref : ALL_REPOSITORIES.values()) { if (ref != null) { RemoteRepository rep = ref.get(); if(rep != null) { result.add(rep); } } } return result; } public boolean isConnected() { return !isOffline(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getAlias() == null) ? 0 : getAlias().hashCode()); int uriModificator = 0; try { uriModificator = (getBaseUrl() == null) ? 0 : getBaseUrl().toURI().hashCode(); } catch (URISyntaxException e) {} result = prime * result + uriModificator; result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; RemoteRepository other = (RemoteRepository) obj; if (getAlias() == null) { if (other.getAlias() != null) return false; } else if (!getAlias().equals(other.getAlias())) return false; if (getBaseUrl() == null) { if (other.getBaseUrl() != null) return false; } else try { if (!getBaseUrl().toURI().equals(other.getBaseUrl().toURI())) return false; } catch (URISyntaxException e) { // this cannot happen, since we already had have a valid URL: no possible problem when converting to uri return false; } if (getUsername() == null) { if (other.getUsername() != null) return false; } else if (!getUsername().equals(other.getUsername())) return false; return true; } /** Returns the URI to which a browser can be pointed to browse a given entry. */ public URI getURIForResource(String path) { try { return getBaseUrl().toURI().resolve("/RA/faces/restricted/browse.xhtml?location=" + URLEncoder.encode(path, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (URISyntaxException e) { throw new RuntimeException(e); } } /** Returns the URI to which a browser can be pointed to access the RA web interface. */ private URI getURIWebInterfaceURI() { try { return getBaseUrl().toURI().resolve("RA/faces/restricted/index.xhtml"); } catch (URISyntaxException e) { throw new RuntimeException(e); } } public void browse(String location) { try { Desktop.getDesktop().browse(getURIForResource(location)); } catch (Exception e) { SwingTools.showSimpleErrorMessage("cannot_open_browser", e); } } public void showLog(int id) { try { Desktop.getDesktop().browse(getProcessLogURI(id)); } catch (Exception e) { SwingTools.showSimpleErrorMessage("cannot_open_browser", e); } } public URI getProcessLogURI(int id) { try { return getBaseUrl().toURI().resolve("/RA/processlog?id=" + id); } catch (URISyntaxException e) { throw new RuntimeException(e); } } @Override public Collection<Action> getCustomActions() { Collection<Action> actions = super.getCustomActions(); actions.add(new BrowseAction("remoterepository.administer", getRepository().getURIWebInterfaceURI())); return actions; } @Override public boolean shouldSave() { return !isHome; } // JDBC entries provided by server private Collection<FieldConnectionEntry> fetchJDBCEntries() throws XMLException, CipherException, SAXException, IOException { URL xmlURL = new URL(getBaseUrl(), "RAWS/jdbc_connections.xml"); Document doc = XMLTools.parse(WebServiceTools.openStreamFromURL(xmlURL)); final Collection<FieldConnectionEntry> result = DatabaseConnectionService.parseEntries(doc.getDocumentElement()); for (FieldConnectionEntry entry : result) { entry.setRepository(getAlias()); } return result; } @Override public void postInstall() {} private void installJDBCConnectionEntries() throws RepositoryException { if (this.connectionEntries != null) { return; } try { this.connectionEntries = fetchJDBCEntries(); for (FieldConnectionEntry entry : connectionEntries) { DatabaseConnectionService.addConnectionEntry(entry); } LogService.getRoot().log(Level.CONFIG, "com.rapidminer.repository.remote.RemoteRepository.added_jdbc_connections_exported_by", new Object[] { connectionEntries.size(), getName() }); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.repository.remote.RemoteRepository.fetching_jdbc_connections_entries_error", getName()), e); setPassword(null); setOffline(true); throw new RepositoryException(I18N.getMessage(I18N.getErrorBundle(), "repository.cannot_be_reached", getName()), e); } } private void removeJDBCConnectionEntries() { if (this.connectionEntries != null) { for (FieldConnectionEntry entry : connectionEntries) { DatabaseConnectionService.deleteConnectionEntry(entry); } this.connectionEntries = null; } } @Override public void preRemove() {} public URL getBaseUrl() { return baseUrl; } public void setBaseUrl(URL url) { baseUrl = url; } public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } public void setPassword(char[] password) { if (password != null && password.length == 0) { this.password = null; } else { this.password = password; } WebServiceTools.clearAuthCache(); } @Override public boolean isConfigurable() { return true; } @Override public RepositoryConfigurationPanel makeConfigurationPanel() { return new RemoteRepositoryPanel(); } public void setAlias(String alias) { this.alias = alias; } public String getAlias() { return alias; } private boolean isOffline() { return offline; } /** If value changes, notifies {@link #connectionListeners}. */ private void setOffline(boolean offline) { if (offline == this.offline) { return; } this.offline = offline; for (ConnectionListener l : connectionListeners) { if (isConnected()) { l.connectionEstablished(this); } else { l.connectionLost(this); } } } private List<ConnectionListener> connectionListeners = new LinkedList<ConnectionListener>(); public void addConnectionListener(ConnectionListener l) { connectionListeners.add(l); } public void removeConnectionListener(ConnectionListener l) { connectionListeners.remove(l); } /** * @return the passwortInputCanceled */ public boolean isPasswordInputCanceled() { return this.passwortInputCanceled; } /** * @param passwortInputCanceled the passwortInputCanceled to set */ public void setPasswortInputCanceled(boolean passwortInputCanceled) { this.passwortInputCanceled = passwortInputCanceled; } /** * @return the protocollExceptionCount */ public int getProtocollExceptionCount() { return this.protocollExceptionCount; } /** * @param protocollExceptionCount the protocollExceptionCount to set */ public void setProtocollExceptionCount(int protocollExceptionCount) { this.protocollExceptionCount = protocollExceptionCount; } }