Java tutorial
package ca.sqlpower.architect.enterprise; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; import javax.annotation.Nonnull; import javax.swing.SwingUtilities; import org.apache.commons.codec.binary.Hex; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCookieStore; import org.apache.log4j.Logger; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.olap4j.metadata.Datatype; import org.springframework.security.AccessDeniedException; import ca.sqlpower.architect.ArchitectSession; import ca.sqlpower.architect.ArchitectSessionContext; import ca.sqlpower.architect.ArchitectSessionImpl; import ca.sqlpower.architect.SnapshotCollection; import ca.sqlpower.architect.ddl.DDLGenerator; import ca.sqlpower.architect.swingui.ArchitectSwingProject; import ca.sqlpower.architect.swingui.ArchitectSwingSessionContext; import ca.sqlpower.dao.SPPersistenceException; import ca.sqlpower.dao.SPPersisterListener; import ca.sqlpower.dao.json.SPJSONMessageDecoder; import ca.sqlpower.dao.json.SPJSONPersister; import ca.sqlpower.dao.session.SessionPersisterSuperConverter; import ca.sqlpower.diff.DiffChunk; import ca.sqlpower.diff.DiffInfo; import ca.sqlpower.diff.SimpleDiffChunkJSONConverter; import ca.sqlpower.enterprise.ClientSideSessionUtils; import ca.sqlpower.enterprise.DataSourceCollectionUpdater; import ca.sqlpower.enterprise.JSONMessage; import ca.sqlpower.enterprise.JSONResponseHandler; import ca.sqlpower.enterprise.ServerInfoProvider; import ca.sqlpower.enterprise.TransactionInformation; import ca.sqlpower.enterprise.client.ProjectLocation; import ca.sqlpower.enterprise.client.RevisionController; import ca.sqlpower.enterprise.client.SPServerInfo; import ca.sqlpower.enterprise.client.User; import ca.sqlpower.object.AbstractPoolingSPListener; import ca.sqlpower.object.AbstractSPListener; import ca.sqlpower.object.SPChildEvent; import ca.sqlpower.object.SPObject; import ca.sqlpower.object.SPObjectSnapshot; import ca.sqlpower.object.SPObjectUUIDComparator; import ca.sqlpower.sql.DataSourceCollection; import ca.sqlpower.sql.JDBCDataSource; import ca.sqlpower.sql.PlDotIni; import ca.sqlpower.sql.SPDataSource; import ca.sqlpower.sql.SpecificDataSourceCollection; import ca.sqlpower.sqlobject.SQLObjectException; import ca.sqlpower.sqlobject.UserDefinedSQLType; import ca.sqlpower.sqlobject.UserDefinedSQLTypeSnapshot; import ca.sqlpower.swingui.event.SessionLifecycleEvent; import ca.sqlpower.swingui.event.SessionLifecycleListener; import ca.sqlpower.util.DefaultUserPrompterFactory; import ca.sqlpower.util.SQLPowerUtils; import ca.sqlpower.util.TransactionEvent; import ca.sqlpower.util.UserPrompterFactory; import ca.sqlpower.util.UserPrompter.UserPromptOptions; import ca.sqlpower.util.UserPrompter.UserPromptResponse; public class ArchitectClientSideSession extends ArchitectSessionImpl implements RevisionController { private static Logger logger = Logger.getLogger(ArchitectClientSideSession.class); private static CookieStore cookieStore = new BasicCookieStore(); public static final String MONDRIAN_SCHEMA_REL_PATH = "/mondrian"; /** * The prefs node that will store information about the current settings of * the DDL generator and compare DM panels. Currently this is stored in prefs * because we want to store it per user for each project they are using. In the * future we may want to store this in the server, once per user per project. */ private final Preferences prefs = Preferences.userNodeForPackage(ArchitectClientSideSession.class); /** * Describes the location of the project that this session represents. */ private final ProjectLocation projectLocation; /** * An {@link HttpClient} used to send updates to the server for changes to * the project and to receive updates from other users from the server. */ private final HttpClient outboundHttpClient; /** * The persister that will update the project in this session with changes from * the server. */ private final ArchitectSessionPersister sessionPersister; /** * Used to convert JSON sent from the server into persist calls to forward the * server changes to the {@link #sessionPersister}. */ private final SPJSONPersister jsonPersister; private final ArchitectNetworkConflictResolver updater; private final SPJSONMessageDecoder jsonMessageDecoder; private final DataSourceCollectionUpdater dataSourceCollectionUpdater; private DataSourceCollection<JDBCDataSource> dataSourceCollection; /** * Used to store sessions which hold nothing but security info. */ public static Map<String, ArchitectClientSideSession> securitySessions; private AbstractPoolingSPListener deletionListener; /** * The executor to use as the foreground thread manager. If this is not null * and there is no EDT available this executor will be used to ensure the * system is single threaded. */ private final ThreadPoolExecutor foregroundThreadExecutor; /** * This is false except for testing purposes when the single thread executor * may want to be used if the test is running headless. */ private final boolean useThreadPool; /** * The threads used by {@link #foregroundThreadExecutor} to keep Architect * single threaded if we are using the {@link #foregroundThreadExecutor}. * The executor will only allow one of these threads to be used at a time. */ private final Set<Thread> foregroundExecutorThread = Collections.synchronizedSet(new HashSet<Thread>()); static { securitySessions = new HashMap<String, ArchitectClientSideSession>(); } public ArchitectClientSideSession(ArchitectSessionContext context, String name, ProjectLocation projectLocation) throws SQLObjectException { this(context, name, projectLocation, false); } /** * This constructor is only used for testing. This constructor allows users * to specify an executor to use as the foreground thread instead of using * the normal EDT. This is handy for ensuring all of the events occur on the * correct thread and updates do not conflict with persists. If the executor * is null then the foreground thread will just execute the runnables on the * current thread. */ public ArchitectClientSideSession(ArchitectSessionContext context, String name, ProjectLocation projectLocation, boolean useThreadPool) throws SQLObjectException { super(context, name, new ArchitectSwingProject()); this.projectLocation = projectLocation; this.useThreadPool = useThreadPool; this.foregroundThreadExecutor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread newThread = new Thread(r); foregroundExecutorThread.add(newThread); return newThread; } }); foregroundThreadExecutor.allowCoreThreadTimeOut(false); dataSourceCollectionUpdater = new ArchitectDataSourceCollectionUpdater(projectLocation); this.isEnterpriseSession = true; setupSnapshots(); String ddlgClass = prefs.get(this.projectLocation.getUUID() + ".ddlg", null); if (ddlgClass != null) { try { DDLGenerator ddlg = (DDLGenerator) Class .forName(ddlgClass, true, ArchitectClientSideSession.class.getClassLoader()).newInstance(); setDDLGenerator(ddlg); ddlg.setTargetCatalog(prefs.get(this.projectLocation.getUUID() + ".targetCatalog", null)); ddlg.setTargetSchema(prefs.get(this.projectLocation.getUUID() + ".targetSchema", null)); } catch (Exception e) { createUserPrompter("Cannot load DDL settings due to missing class " + ddlgClass, UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, null, "OK"); logger.error("Cannot find DDL Generator for class " + ddlgClass + ", ddl generator properties are not loaded."); } } outboundHttpClient = ClientSideSessionUtils.createHttpClient(projectLocation.getServiceInfo(), cookieStore); dataSourceCollection = getDataSources(); sessionPersister = new ArchitectSessionPersister("inbound-" + projectLocation.getUUID(), getWorkspace(), new ArchitectPersisterSuperConverter(dataSourceCollection, getWorkspace())); sessionPersister.setWorkspaceContainer(this); jsonMessageDecoder = new SPJSONMessageDecoder(sessionPersister); updater = new ArchitectNetworkConflictResolver(projectLocation, jsonMessageDecoder, ClientSideSessionUtils.createHttpClient(projectLocation.getServiceInfo(), cookieStore), outboundHttpClient, this); jsonPersister = new SPJSONPersister(updater); verifyServerLicense(projectLocation); } protected void verifyServerLicense(ProjectLocation projectLocation) throws AssertionError { try { ServerInfoProvider.getServerVersion(projectLocation.getServiceInfo().getServerAddress(), String.valueOf(projectLocation.getServiceInfo().getPort()), projectLocation.getServiceInfo().getPath(), projectLocation.getServiceInfo().getUsername(), projectLocation.getServiceInfo().getPassword(), cookieStore); } catch (Exception e) { throw new AssertionError("Exception encountered while verifying the server license:" + e.getMessage()); } } /** * Helper method for the constructor of a client side session. * <p> * This method will attach listeners and update snapshots as necessary on * the project's load. */ private void setupSnapshots() { getTargetDatabase().addSPListener(new SPObjectSnapshotHierarchyListener(this)); //This listener will update the obsolete flag on snapshots being added //to the workspace to keep old snapshot's obsolete flag correct when //they haven't been opened in some time but updates have occurred to the types. //While this will also check new snapshots being added to the system doing //the check doesn't hurt anything. final AbstractPoolingSPListener obsolescenceListener = new AbstractPoolingSPListener(false) { @Override public void childAddedImpl(SPChildEvent e) { if (e.getChild() instanceof UserDefinedSQLTypeSnapshot) { UserDefinedSQLTypeSnapshot snapshot = (UserDefinedSQLTypeSnapshot) e.getChild(); UserDefinedSQLType systemType = findSystemTypeFromSnapshot(snapshot); if (systemType == null) { snapshot.setDeleted(true); } else { if (!UserDefinedSQLType.areEqual(snapshot.getSPObject(), systemType)) { snapshot.setObsolete(true); } snapshot.setDeleted(false); } } else if (e.getChild() instanceof DomainCategorySnapshot) { DomainCategorySnapshot snapshot = (DomainCategorySnapshot) e.getChild(); boolean deleted = true; for (DomainCategory category : getSystemWorkspace().getDomainCategories()) { if (category.getUUID().equals(snapshot.getOriginalUUID())) { deleted = false; if (!DomainCategory.areEqual(snapshot.getSPObject(), category)) { snapshot.setObsolete(true); deleted = true; } } } snapshot.setDeleted(deleted); } } }; getWorkspace().getSnapshotCollection().addSPListener(obsolescenceListener); getWorkspace().addSPListener(new AbstractSPListener() { @Override public void transactionStarted(TransactionEvent e) { obsolescenceListener.transactionStarted(TransactionEvent.createStartTransactionEvent( getWorkspace().getSnapshotCollection(), "Simulated begin: " + e.getMessage())); } @Override public void transactionEnded(TransactionEvent e) { obsolescenceListener.transactionEnded(TransactionEvent.createEndTransactionEvent( getWorkspace().getSnapshotCollection(), "Simulated commit: " + e.getMessage())); } @Override public void transactionRollback(TransactionEvent e) { obsolescenceListener.transactionRollback(TransactionEvent.createRollbackTransactionEvent( getWorkspace().getSnapshotCollection(), "Simulated rollback: " + e.getMessage())); } }); // If this returns null, it means the system session hasn't been // initialized yet. This means we are the system session, and attaching // the listener is unnecessary. if (getSystemSession() != null) { deletionListener = new AbstractPoolingSPListener() { @Override protected void childAddedImpl(SPChildEvent e) { if (e.getChild() instanceof DomainCategory) { e.getChild().addSPListener(deletionListener); } else { for (SPObjectSnapshot<SPObject> snapshot : getWorkspace().getSnapshotCollection() .getChildren(SPObjectSnapshot.class)) { if (snapshot.getOriginalUUID().equals(e.getChild().getUUID())) { snapshot.setDeleted(false); } } } } @Override protected void childRemovedImpl(SPChildEvent e) { if (e.getChild() instanceof DomainCategory) { e.getChild().removeSPListener(deletionListener); } else { for (SPObjectSnapshot<SPObject> snapshot : getWorkspace().getSnapshotCollection() .getChildren(SPObjectSnapshot.class)) { if (snapshot.getOriginalUUID().equals(e.getChild().getUUID())) { snapshot.setDeleted(true); } } } } }; getSystemWorkspace().addSPListener(deletionListener); for (DomainCategory cat : getSystemWorkspace().getChildren(DomainCategory.class)) { cat.addSPListener(deletionListener); } } } /** * Map of server addresses to system workspaces. Use * {@link SPServerInfo#getServerAddress()} as the key. */ public static Map<String, ArchitectClientSideSession> getSecuritySessions() { return securitySessions; } @Override public boolean close() { if (getDDLGenerator() != null) { if (getDDLGenerator().getTargetCatalog() != null) { prefs.put(projectLocation.getUUID() + ".targetCatalog", getDDLGenerator().getTargetCatalog()); } if (getDDLGenerator().getTargetSchema() != null) { prefs.put(projectLocation.getUUID() + ".targetSchema", getDDLGenerator().getTargetSchema()); } prefs.put(projectLocation.getUUID() + ".ddlg", getDDLGenerator().getClass().getName()); } try { //TODO: Figure out how to de-register the session &c. } catch (Exception e) { try { logger.error(e); createUserPrompter("Cannot access the server to close the server session", UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, UserPromptResponse.OK, "OK"); } catch (Throwable t) { //do nothing here because we failed on logging the error. } } updater.interrupt(); outboundHttpClient.getConnectionManager().shutdown(); if (dataSourceCollection != null) { dataSourceCollectionUpdater.detach(dataSourceCollection); } getSystemWorkspace().removeSPListener(deletionListener); for (DomainCategory cat : getSystemWorkspace().getChildren(DomainCategory.class)) { cat.removeSPListener(deletionListener); } return super.close(); } @Override public DataSourceCollection<JDBCDataSource> getDataSources() { if (dataSourceCollection == null) { dataSourceCollection = getDataSourcesFromServer(); dataSourceCollectionUpdater.attach(dataSourceCollection); } return dataSourceCollection; } private DataSourceCollection<JDBCDataSource> getDataSourcesFromServer() { ResponseHandler<DataSourceCollection<JDBCDataSource>> plIniHandler = new ResponseHandler<DataSourceCollection<JDBCDataSource>>() { public DataSourceCollection<JDBCDataSource> handleResponse(HttpResponse response) throws ClientProtocolException, IOException { if (response.getStatusLine().getStatusCode() == 401) { throw new AccessDeniedException("Access Denied"); } if (response.getStatusLine().getStatusCode() != 200) { throw new IOException("Server error while reading data sources: " + response.getStatusLine()); } PlDotIni plIni; try { plIni = new PlDotIni( ClientSideSessionUtils.getServerURI(projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG + "/jdbc/"), ClientSideSessionUtils.getServerURI(projectLocation.getServiceInfo(), MONDRIAN_SCHEMA_REL_PATH)) { @Override public List<UserDefinedSQLType> getSQLTypes() { List<UserDefinedSQLType> types = new ArrayList<UserDefinedSQLType>(); types.addAll(ArchitectClientSideSession.this.getSQLTypes()); types.addAll(ArchitectClientSideSession.this.getDomains()); return types; } @Override public UserDefinedSQLType getNewSQLType(String name, int jdbcCode) { UserDefinedSQLType newType = new UserDefinedSQLType(); newType.setName(name); newType.setType(jdbcCode); ArchitectSwingProject systemWorkspace = ArchitectClientSideSession.this .getSystemWorkspace(); systemWorkspace.addChild(newType, systemWorkspace.getChildren(UserDefinedSQLType.class).size()); return newType; } public SPDataSource getDataSource(String name) { SPDataSource ds = super.getDataSource(name); if (ds == null) { mergeNewDataSources(); return super.getDataSource(name); } else { return ds; } } public <C extends SPDataSource> C getDataSource(String name, java.lang.Class<C> classType) { C ds = super.getDataSource(name, classType); if (ds == null) { mergeNewDataSources(); return super.getDataSource(name, classType); } else { return ds; } } private void mergeNewDataSources() { DataSourceCollection<JDBCDataSource> dsc = getDataSourcesFromServer(); for (SPDataSource merge : dsc.getConnections()) { mergeDataSource(merge); } } }; plIni.read(response.getEntity().getContent()); logger.debug("Data source collection has URI " + plIni.getServerBaseURI()); } catch (URISyntaxException e) { throw new RuntimeException(e); } return new SpecificDataSourceCollection<JDBCDataSource>(plIni, JDBCDataSource.class); } }; DataSourceCollection<JDBCDataSource> dsc; try { dsc = ClientSideSessionUtils.executeServerRequest(outboundHttpClient, projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG + "/data-sources/", plIniHandler); } catch (AccessDeniedException e) { throw e; } catch (Exception ex) { throw new RuntimeException(ex); } return dsc; } public void startUpdaterThread() { final SPPersisterListener listener = new SPPersisterListener(jsonPersister, sessionPersister, new ArchitectPersisterSuperConverter(dataSourceCollection, getWorkspace())); SQLPowerUtils.listenToHierarchy(getWorkspace(), listener); updater.setListener(listener); updater.setConverter(new SessionPersisterSuperConverter(dataSourceCollection, getWorkspace())); updater.start(); addSessionLifecycleListener(new SessionLifecycleListener<ArchitectSession>() { public void sessionClosing(SessionLifecycleEvent<ArchitectSession> e) { SQLPowerUtils.unlistenToHierarchy(getWorkspace(), listener); } public void sessionOpening(SessionLifecycleEvent<ArchitectSession> e) { } }); } public User getUser() { String username = getProjectLocation().getServiceInfo().getUsername(); User currentUser = null; for (User user : getSystemWorkspace().getChildren(User.class)) { if (user.getUsername().equals(username)) { currentUser = user; } } return currentUser; } public void persistProjectToServer() throws SPPersistenceException { final SPPersisterListener tempListener = new SPPersisterListener(jsonPersister, new ArchitectPersisterSuperConverter(dataSourceCollection, getWorkspace())); tempListener.persistObject(getWorkspace(), 0); } public ArchitectSwingProject getSystemWorkspace() { return getSecuritySessions().get(getProjectLocation().getServiceInfo().getServerAddress()).getWorkspace(); } public ArchitectClientSideSession getSystemSession() { return getSecuritySessions().get(getProjectLocation().getServiceInfo().getServerAddress()); } @Override public void runInForeground(Runnable runner) { // If we're in a SwingContext, run on the Swing Event Dispatch thread. // XXX: This is a bit of a quickfix and I think a better way to possibly fix // this could be to have WabitServerSession implement WabitSession, and // use a delegate session to delegate most of the server calls (instead // of extending WabitSessionImpl). Then if it's in a swing context, it would // have a WabitSwingSession instead. if (getContext() instanceof ArchitectSwingSessionContext) { SwingUtilities.invokeLater(runner); } else if (useThreadPool) { foregroundThreadExecutor.execute(runner); } else { super.runInForeground(runner); } } @Override public boolean isForegroundThread() { if (useThreadPool) { return foregroundExecutorThread.contains(Thread.currentThread()); } else { return super.isForegroundThread(); } } /** * Exposes the shared cookie store so we don't spawn useless sessions * through the client. */ public static CookieStore getCookieStore() { return cookieStore; } public ProjectLocation getProjectLocation() { return projectLocation; } public List<TransactionInformation> getTransactionList(long fromVersion, long toVersion) throws IOException, URISyntaxException, JSONException, ParseException { SPServerInfo serviceInfo = projectLocation.getServiceInfo(); HttpClient httpClient = ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore); try { logger.info("Getting transactions between " + fromVersion + " and " + toVersion); JSONMessage message = ClientSideSessionUtils.executeServerRequest(httpClient, projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG + "/project/" + projectLocation.getUUID() + "/revision_list", "versions=" + fromVersion + ":" + toVersion, new JSONResponseHandler()); return ClientSideSessionUtils.decodeJSONRevisionList(message.getBody()); } finally { httpClient.getConnectionManager().shutdown(); } } public static ProjectLocation uploadProject(SPServerInfo serviceInfo, String name, File project, UserPrompterFactory session) throws URISyntaxException, ClientProtocolException, IOException, JSONException { return ClientSideSessionUtils.uploadProject(serviceInfo, name, project, session, cookieStore); } public int revertServerWorkspace(int revisionNo) throws IOException, URISyntaxException, JSONException { return revertServerWorkspace(projectLocation, revisionNo); } /** * This method reverts the server workspace specified by the given project location * to the specified revision number. * * All sessions should automatically update to the reverted revision due to their Updater. * * @returns The new global revision number, right after the reversion, or -1 if the server did not revert. * @throws IOException * @throws URISyntaxException * @throws JSONException */ public static int revertServerWorkspace(ProjectLocation projectLocation, int revisionNo) throws IOException, URISyntaxException, JSONException { return ClientSideSessionUtils.revertServerWorkspace(projectLocation, revisionNo, cookieStore); } /** * This method can update any users password on the server given the correct * old password and done by a user with the privileges to change the user's * password. * * @param session * The client session that has the correct server information to * post requests to the server. * @param username * The user name of the user to update. * @param oldPassword * The old password of the user to validate that the password can * be updated correctly. * @param newPassword * The new password to update to. * @param upf * A user prompter to display message and error information to * the user as necessary. */ public void updateUserPassword(User user, String oldPassword, String newPassword, UserPrompterFactory upf) { SPServerInfo serviceInfo = getProjectLocation().getServiceInfo(); HttpClient client = ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore); MessageDigest digester; try { digester = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } try { JSONObject begin = new JSONObject(); begin.put("uuid", JSONObject.NULL); begin.put("method", "begin"); JSONObject persist = new JSONObject(); persist.put("uuid", user.getUUID()); persist.put("propertyName", "password"); persist.put("type", Datatype.STRING.toString()); if (oldPassword == null) { persist.put("method", "persistProperty"); } else { persist.put("method", "changeProperty"); persist.put("oldValue", new String(Hex.encodeHex(digester.digest(oldPassword.getBytes())))); } persist.put("newValue", new String(Hex.encodeHex(digester.digest(newPassword.getBytes())))); JSONObject commit = new JSONObject(); commit.put("uuid", JSONObject.NULL); commit.put("method", "commit"); JSONArray transaction = new JSONArray(); transaction.put(begin); transaction.put(persist); transaction.put(commit); URI serverURI = new URI("http", null, serviceInfo.getServerAddress(), serviceInfo.getPort(), serviceInfo.getPath() + "/" + ClientSideSessionUtils.REST_TAG + "/project/system", "currentRevision=" + getCurrentRevisionNumber(), null); HttpPost postRequest = new HttpPost(serverURI); postRequest.setEntity(new StringEntity(transaction.toString())); postRequest.setHeader("Content-Type", "application/json"); HttpUriRequest request = postRequest; JSONMessage result = client.execute(request, new JSONResponseHandler()); if (result.getStatusCode() != 200) { logger.warn("Failed password change"); if (result.getStatusCode() == 412) { upf.createUserPrompter("The password you have entered is incorrect.", UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, "OK", "OK").promptUser(""); } else { upf.createUserPrompter( "Could not change the password due to the following: " + result.getBody() + " See logs for more details.", UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, "OK", "OK") .promptUser(""); } } else { upf.createUserPrompter( "Password successfully changed. Please log into open projects" + " with your new password.", UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, "OK", "OK") .promptUser(""); } } catch (AccessDeniedException ex) { logger.warn("Failed password change", ex); upf.createUserPrompter("The password you have entered is incorrect.", UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, "OK", "OK").promptUser(""); } catch (Exception ex) { logger.warn("Failed password change", ex); upf.createUserPrompter( "Could not change the password due to the following: " + ex.getMessage() + " See logs for more details.", UserPromptType.MESSAGE, UserPromptOptions.OK, UserPromptResponse.OK, "OK", "OK").promptUser(""); } } public void persistRevisionFromServer(int revisionNo, SPJSONMessageDecoder targetDecoder) throws IOException, URISyntaxException, SPPersistenceException, IllegalArgumentException { persistRevisionFromServer(projectLocation, revisionNo, targetDecoder); } /** * Gets a list of DiffChunks representing the differences between the two revisions from the server. */ public List<DiffChunk<DiffInfo>> getComparisonDiffChunks(int oldRevisionNo, int newRevisionNo) throws IOException, URISyntaxException, JSONException, SPPersistenceException { SPServerInfo serviceInfo = projectLocation.getServiceInfo(); HttpClient httpClient = ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore); try { JSONMessage response = ClientSideSessionUtils.executeServerRequest(httpClient, projectLocation.getServiceInfo(), "/" + ClientSideSessionUtils.REST_TAG + "/project/" + projectLocation.getUUID() + "/compare", "versions=" + oldRevisionNo + ":" + newRevisionNo, new JSONResponseHandler()); return SimpleDiffChunkJSONConverter.decode(response.getBody()); } finally { httpClient.getConnectionManager().shutdown(); } } public static HttpClient createHttpClient(SPServerInfo serviceInfo) { return ClientSideSessionUtils.createHttpClient(serviceInfo, cookieStore); } public static void persistRevisionFromServer(ProjectLocation projectLocation, int revisionNo, SPJSONMessageDecoder decoder) throws IOException, URISyntaxException, SPPersistenceException, IllegalArgumentException { ClientSideSessionUtils.persistRevisionFromServer(projectLocation, revisionNo, decoder, cookieStore); } public static ProjectLocation createNewServerSession(SPServerInfo serviceInfo, String name, ArchitectSession session) throws URISyntaxException, ClientProtocolException, IOException, JSONException { return ClientSideSessionUtils.createNewServerSession(serviceInfo, name, cookieStore, new DefaultUserPrompterFactory()); } public static List<ProjectLocation> getWorkspaceNames(SPServerInfo serviceInfo, UserPrompterFactory upf) throws IOException, URISyntaxException, JSONException { return ClientSideSessionUtils.getWorkspaceNames(serviceInfo, cookieStore, upf); } public static void deleteServerWorkspace(ProjectLocation projectLocation, ArchitectSession session) throws URISyntaxException, ClientProtocolException, IOException { ClientSideSessionUtils.deleteServerWorkspace(projectLocation, cookieStore, new DefaultUserPrompterFactory()); } public ArchitectNetworkConflictResolver getUpdater() { return updater; } // ----------- Preferences accessors and mutators ----------- // XXX Add different types, such as int or boolean, as needed /** * Enters a double value as a preference for this server session. * It will be able to be loaded by this local user in the future. */ public void putPref(String prefName, double pref) { prefs.putDouble(projectLocation.getUUID() + "." + prefName, pref); } /** * Enters a String as a preference for this server session. * It will be able to be loaded by this local user in the future. */ public void putPref(String prefName, String pref) { prefs.put(projectLocation.getUUID() + "." + prefName, pref); } /** * Retrieves a locally saved preference of type double. * @param prefName The name of the previously saved preference * @return The previously saved preference, or 0 if none exists yet */ public double getPrefDouble(String prefName) { return getPrefDouble(prefName, 0); } /** * Retrieves a locally saved preference of type double. * @param prefName The name of the previously saved preference * @param def The value this function should return * if no preference was previously saved */ public double getPrefDouble(String prefName, double def) { return prefs.getDouble(projectLocation.getUUID() + "." + prefName, def); } /** * Retrieves a locally saved preference of type String. * @param prefName The name of the previously saved preference * @return The previously saved preference, or null if none exists yet */ public String getPref(String prefName) { return getPref(prefName, null); } /** * Retrieves a locally saved preference of type String. * @param prefName The name of the previously saved preference * @param def The value this function should return * if no preference was previously saved */ public String getPref(String prefName, String def) { return prefs.get(projectLocation.getUUID() + "." + prefName, def); } public int getCurrentRevisionNumber() { return updater.getRevision(); } /* * Do not remove this method without first changing the custom PlDotini in getDataSources(). * Doing so will cause infinite recursion when accessing the SQLTypes. */ @Override public List<UserDefinedSQLType> getSQLTypes() { // The following was my attempt to merge the snapshot and system types lists together // without making it O(mn), but the code is a bit lengthier than I'd like, so perhaps // the added complexity may not be worth it? SnapshotCollection collection = getWorkspace().getSnapshotCollection(); List<UserDefinedSQLTypeSnapshot> typeSnapshots = new ArrayList<UserDefinedSQLTypeSnapshot>( collection.getChildren(UserDefinedSQLTypeSnapshot.class)); List<UserDefinedSQLType> systemTypes = new ArrayList<UserDefinedSQLType>( getSystemWorkspace().getChildren(UserDefinedSQLType.class)); // Remove domain snapshots from the list for (int i = typeSnapshots.size() - 1; i >= 0; i--) { UserDefinedSQLTypeSnapshot snapshot = typeSnapshots.get(i); if (snapshot.isDomainSnapshot()) { typeSnapshots.remove(i); } } // If there are no snapshots, just return the system types. if (typeSnapshots.size() == 0) return Collections.unmodifiableList(systemTypes); // Sort both lists by the UUIDs of the system types Collections.sort(typeSnapshots, new Comparator<UserDefinedSQLTypeSnapshot>() { public int compare(UserDefinedSQLTypeSnapshot o1, UserDefinedSQLTypeSnapshot o2) { return o1.getOriginalUUID().compareTo(o2.getOriginalUUID()); } }); Collections.sort(systemTypes, new SPObjectUUIDComparator<UserDefinedSQLType>()); // Now go through the list of system types. If a snapshot type's // original UUID matches, then replace the system type with the snapshot. Iterator<UserDefinedSQLTypeSnapshot> snapshotIterator = typeSnapshots.iterator(); UserDefinedSQLTypeSnapshot currentSnapshot = snapshotIterator.next(); for (int i = 0; i < systemTypes.size(); i++) { UserDefinedSQLType type = systemTypes.get(i); int compareTo = currentSnapshot.getOriginalUUID().compareTo(type.getUUID()); if (compareTo <= 0) { if (compareTo == 0) { systemTypes.set(i, currentSnapshot.getSPObject()); } else { systemTypes.add(i, currentSnapshot.getSPObject()); } if (snapshotIterator.hasNext() && i != systemTypes.size() - 1) { currentSnapshot = snapshotIterator.next(); } else { break; } } } // If we've gone through all the system types, then append the remaining snapshot types while (snapshotIterator.hasNext()) { currentSnapshot = snapshotIterator.next(); systemTypes.add(currentSnapshot.getSPObject()); } return Collections.unmodifiableList(systemTypes); } /** * Returns the {@link List} of {@link UserDefinedSQLType}s in this session * that are children of a {@link DomainCategory}. Each domain in the system * will be in the list once as either the domain that is in the system * workspace or a snapshot of the domain that is in the current project. UDTs * that are children of a category are defined as domains instead of types. */ @Override public List<UserDefinedSQLType> getDomains() { // The following was my attempt to merge the snapshot and system category lists together // without making it O(nm), but the code is a bit lengthier than I'd like, so perhaps // the added complexity may not be worth it? SnapshotCollection collection = getWorkspace().getSnapshotCollection(); List<UserDefinedSQLTypeSnapshot> typeSnapshots = new ArrayList<UserDefinedSQLTypeSnapshot>( collection.getChildren(UserDefinedSQLTypeSnapshot.class)); List<DomainCategory> systemCategories = new ArrayList<DomainCategory>( getSystemWorkspace().getChildren(DomainCategory.class)); List<UserDefinedSQLTypeSnapshot> domainSnapshots = new ArrayList<UserDefinedSQLTypeSnapshot>(); for (UserDefinedSQLTypeSnapshot udtSnapshot : typeSnapshots) { if (udtSnapshot.isDomainSnapshot()) { domainSnapshots.add(udtSnapshot); } } List<UserDefinedSQLType> systemDomains = new ArrayList<UserDefinedSQLType>(); for (DomainCategory category : systemCategories) { systemDomains.addAll(category.getChildren(UserDefinedSQLType.class)); } // If there are no snapshots, just return the system categories. if (domainSnapshots.size() == 0) return Collections.unmodifiableList(systemDomains); // Sort both lists by the UUIDs of the system categories Collections.sort(domainSnapshots, new Comparator<UserDefinedSQLTypeSnapshot>() { public int compare(UserDefinedSQLTypeSnapshot o1, UserDefinedSQLTypeSnapshot o2) { return o1.getOriginalUUID().compareTo(o2.getOriginalUUID()); } }); Collections.sort(systemDomains, new SPObjectUUIDComparator<UserDefinedSQLType>()); // Now go through the list of system categories. If a snapshot category's // original UUID matches, then replace the system category with the snapshot. Iterator<UserDefinedSQLTypeSnapshot> snapshotIterator = domainSnapshots.iterator(); UserDefinedSQLTypeSnapshot currentSnapshot = snapshotIterator.next(); for (int i = 0; i < systemDomains.size(); i++) { UserDefinedSQLType type = systemDomains.get(i); int compareTo = currentSnapshot.getOriginalUUID().compareTo(type.getUUID()); if (compareTo <= 0) { if (compareTo == 0) { systemDomains.set(i, currentSnapshot.getSPObject()); } else { systemDomains.add(i, currentSnapshot.getSPObject()); } if (snapshotIterator.hasNext() && i != systemDomains.size() - 1) { currentSnapshot = snapshotIterator.next(); } else { break; } } } // If we've gone through all the system types, then append the remaining snapshot categories while (snapshotIterator.hasNext()) { currentSnapshot = snapshotIterator.next(); systemDomains.add(currentSnapshot.getSPObject()); } return Collections.unmodifiableList(systemDomains); } @Override public ArchitectSwingProject getWorkspace() { return (ArchitectSwingProject) super.getWorkspace(); } /** * Finds the UDT that has the same uuid as the given snapshot's * {@link SPObjectSnapshot#getOriginalUUID()} method. The snapshot given * cannot be null. Returns null if no type is found. */ public UserDefinedSQLType findSystemTypeFromSnapshot(@Nonnull SPObjectSnapshot<?> snapshot) { for (UserDefinedSQLType systemType : getSystemWorkspace().getSqlTypes()) { if (systemType.getUUID().equals(snapshot.getOriginalUUID())) { return systemType; } } for (DomainCategory category : getSystemWorkspace().getDomainCategories()) { for (UserDefinedSQLType systemType : category.getChildren(UserDefinedSQLType.class)) { if (systemType.getUUID().equals(snapshot.getOriginalUUID())) { return systemType; } } } return null; } /** * Returns an action that will update the snapshot object in the given * snapshot to be the same as the type the snapshot maps to in the system * workspace. * * @param snapshot * The snapshot to update. This currently only works for * snapshots of UserDefinedSQLTypes but will be extended to * others in the future. */ public Runnable createUpdateSnapshotRunnable(final SPObjectSnapshot<?> snapshot) { return new Runnable() { @Override public void run() { if (snapshot.getSPObject() instanceof DomainCategory) { for (DomainCategory category : getSystemWorkspace().getDomainCategories()) { if (category.getUUID().equals(snapshot.getOriginalUUID())) { ((DomainCategory) snapshot.getSPObject()).updateToMatch(category); snapshot.setObsolete(false); return; } } } if (!(snapshot.getSPObject() instanceof UserDefinedSQLType)) return; UserDefinedSQLType snapshotType = (UserDefinedSQLType) snapshot.getSPObject(); UserDefinedSQLType systemType = findSystemTypeFromSnapshot(snapshot); if (systemType == null) return; snapshotType.updateToMatch(systemType); snapshot.setObsolete(false); } }; } }