Java tutorial
/******************************************************************************* * Copyright (c) 2011 neXtep Software and contributors. * All rights reserved. * * This file is part of neXtep designer. * * NeXtep designer is free software: you can redistribute it * and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, either * version 3 of the License, or any later version. * * NeXtep designer 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Foobar. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * neXtep Softwares - initial API and implementation *******************************************************************************/ package com.nextep.datadesigner.vcs.services; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.apache.commons.collections.map.MultiValueMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProgressMonitor; import org.hibernate.SQLQuery; import org.hibernate.Session; import com.nextep.datadesigner.Designer; import com.nextep.datadesigner.exception.ErrorException; import com.nextep.datadesigner.exception.OutOfDateObjectException; import com.nextep.datadesigner.exception.UnresolvedItemException; import com.nextep.datadesigner.hibernate.HibernateUtil; import com.nextep.datadesigner.impl.NameComparator; import com.nextep.datadesigner.model.ICommand; import com.nextep.datadesigner.model.IElementType; import com.nextep.datadesigner.model.IReference; import com.nextep.datadesigner.model.IReferenceable; import com.nextep.datadesigner.model.ITypedObject; import com.nextep.datadesigner.model.UID; import com.nextep.datadesigner.vcs.impl.RepositoryUser; import com.nextep.datadesigner.vcs.impl.VersionBranch; import com.nextep.designer.core.CorePlugin; import com.nextep.designer.core.model.IParentable; import com.nextep.designer.core.model.IReferenceManager; import com.nextep.designer.vcs.VCSMessages; import com.nextep.designer.vcs.VCSPlugin; import com.nextep.designer.vcs.model.IRepositoryUser; import com.nextep.designer.vcs.model.IVersionBranch; import com.nextep.designer.vcs.model.IVersionContainer; import com.nextep.designer.vcs.model.IVersionInfo; import com.nextep.designer.vcs.model.IVersionable; import com.nextep.designer.vcs.model.IWorkspace; import com.nextep.designer.vcs.model.VersionTree; import com.nextep.designer.vcs.model.impl.Activity; import com.nextep.designer.vcs.model.impl.VersionInfo; import com.nextep.designer.vcs.services.IVersioningService; import com.nextep.designer.vcs.services.IWorkspaceService; /** * This method provides helper methods to deal with version control common operations. * * @author Christophe Fondacci * @author Bruno Gautier */ public final class VersionHelper { private static final Log log = LogFactory.getLog(VersionHelper.class); public static final int PRECISION = IVersionInfo.PRECISION; // Indicates the modulo for a // release number component public static final long REVISION = IVersionInfo.REVISION; public static final long PATCH = IVersionInfo.PATCH; public static final long ITERATION = IVersionInfo.ITERATION; public static final long MINOR = IVersionInfo.MINOR; public static final long MAJOR = IVersionInfo.MAJOR; public static IWorkspace currentView = null; private VersionHelper() { } @SuppressWarnings("unchecked") public static List<IVersionBranch> listBranches() { final List<IVersionBranch> branches = (List<IVersionBranch>) CorePlugin.getIdentifiableDao() .loadAll(VersionBranch.class); Collections.sort(branches, NameComparator.getInstance()); return branches; } /** * Formats a String suffix containing version information. * * @param v version information * @return the formatted version suffix of this version */ public static String getVersionSuffix(IVersionInfo v) { if (v != null) { return " - " + v.getLabel(); //$NON-NLS-1$ } return null; } /** * Generates the full version tree of this version. * * @param version * @return */ public static VersionTree getVersionTree(IVersionable<?> versionable) { // TODO : implement getVersionTree return new VersionTree(versionable.getVersion(), null); } public static boolean ensureModifiable(Object o, boolean raise) { if (o instanceof IVersionable<?>) { o = ((IVersionable<?>) o).getVersionnedObject(); } // Delegating to the neXtep core feature return Designer.checkIsModifiable(o, raise); } /** * Retrieves all versionables of a specified type and return them in a list ordered by Container * name, versionable name. * * @param container container in which to look for versioned elements * @param type element type to extract or <code>null</code> for all types * @return a list of all IVersionable elements */ public static List<IVersionable<?>> getAllVersionables(IVersionContainer container, IElementType type) { List<IVersionable<?>> list = new ArrayList<IVersionable<?>>(); for (IVersionable<?> v : container.getContents()) { // If we have a matching element we add it to the list if (v.getType() == type || type == null) { list.add(v); } // Not having a "else" here because the type could be a CONTAINER or VIEW if (v instanceof IVersionContainer) { list.addAll(getAllVersionables((IVersionContainer) v, type)); } } Collections.sort(list, NameComparator.getInstance()); return list; } /** * This method can retrieve a Versionable object from its model. This avoids unsafe casts and * retrieves an object which could be used for version control. This method will return null if * no versionable is associated to the specified model. * * @param model model for which we want the associated versionable * @return the corresponding versionable */ @SuppressWarnings("unchecked") public static <T> IVersionable<T> getVersionable(T model) { // First we check if we have a self versionable model if (model instanceof IVersionable) { return (IVersionable<T>) model; } else if (model instanceof IParentable<?>) { final Object parent = ((IParentable<?>) model).getParent(); // Unsafe cast, but callers want IVersionable to work on the IVersionable, not on model // so this should be ok return (IVersionable<T>) getVersionable(parent); } else { // TODO Add a version view browsing to retrieve the model return null; } } /** * Retrieves the version info of this object. * * @param o object to retrieve version information for * @return the {@link IVersionInfo} associated with the object or <code>null</code> if this * object doesn't have any version information connected */ public static IVersionInfo getVersionInfo(Object o) { if (o instanceof IVersionInfo) { return (IVersionInfo) o; } else if (o instanceof IVersionable<?>) { return ((IVersionable<?>) o).getVersion(); } else { return null; } } /** * A layer on top of the reference manager which loads version references when they are found */ public static IReferenceable getReferencedItem(IReference ref) { return getReferencedItem(ref, false); } public static IReferenceable getReferencedItem(IReference ref, boolean strict) { UnresolvedItemException unresolvedException = null; List<IReferenceable> filteredRef = Collections.emptyList(); try { final List<IReferenceable> refs = CorePlugin.getService(IReferenceManager.class) .getReferencedItems(ref); // Filtering VersionInfo filteredRef = filterVersionReferences(refs); } catch (UnresolvedItemException e) { // Setting exception unresolvedException = e; } // In non strict mode we do a last try by attempting to get a workspace element if (!strict && (filteredRef.isEmpty() || unresolvedException != null)) { if (ref.isVolatile() && ref.getUID() != null && ref.getUID().rawId() > 0) { // Last try by converting to a non-volatile ref // TODO We should never do this as it could introduce some hardcore bugs // But at the moment, removing that piece will make a lot regression // The good way to go would be to load the referenced item from db. But since we // don't know what kind of elements we need to load, it is very hard to do try { ref.setVolatile(false); log.debug("WARNING: Out of scope reference resolution for " + ref.toString()); //$NON-NLS-1$ final List<IReferenceable> refs = CorePlugin.getService(IReferenceManager.class) .getReferencedItems(ref); filteredRef = filterVersionReferences(refs); } finally { ref.setVolatile(true); } } } // If we have more than 1 referenceable, we fail if (filteredRef.isEmpty()) { throw new UnresolvedItemException( MessageFormat.format(VCSMessages.getString("helper.version.itemReferenceNotFound"), //$NON-NLS-1$ ref.getReferenceId(), (ref.getType() == null ? "null" : ref.getType().getName()), //$NON-NLS-1$ ref.getArbitraryName())); } else if (filteredRef.size() > 1) { throw new ErrorException(MessageFormat.format(VCSMessages.getString("helper.version.tooManyReferences"), //$NON-NLS-1$ ref.getReferenceId(), ref.getType().getName(), filteredRef.size())); } return filteredRef.iterator().next(); } /** * Filters any object which is a {@link IVersionInfo} from the specified referenceabl list. * * @param refs the initial referenceable list * @return the input list cleaned from any {@link IVersionInfo} instance */ private static List<IReferenceable> filterVersionReferences(List<IReferenceable> refs) { List<IReferenceable> filteredRefs = new ArrayList<IReferenceable>(); for (IReferenceable r : refs) { if (!(r instanceof IVersionInfo)) { filteredRefs.add(r); } } return filteredRefs; } /** * @deprecated Use {@link Designer#getReferencedItem(IVersionContainer,IReference)} instead */ // public static IReferenceable getReferencedItem(IVersionContainer container, IReference ref) { // return CorePlugin.getService(IReferenceManager.class).getReferencedItem(container, ref); // } /** * Incremental release alogrythm: This method will compute the next release to come. * * @param release version to increment * @param releaseType type of release to increment, choose a constant PATCH,ITERATION,MINOR or * MAJOR. */ public static void incrementRelease(IVersionInfo release, long releaseType) { long fullVersion = computeVersion(release); long incrementedRelease = incrementRelease(fullVersion, releaseType); release.setRelease(incrementedRelease, true); } /** * Incremental release alogrythm: This method will compute the next release to come. * * @param release version to increment * @param releaseType type of release to increment, choose a constant PATCH,ITERATION,MINOR or * MAJOR. */ public static long incrementRelease(long releaseNumber, long releaseType) { long fullVersion = releaseNumber; fullVersion += releaseType; // Resetting any low precision numbers fullVersion = (fullVersion / releaseType) * releaseType; return fullVersion; } /** * Computes the release full number from its version information * * @param release * @return the full integer value of this release */ public static long computeVersion(IVersionInfo release) { if (release == null) { return 0l; } else { return computeVersion(release.getMajorRelease(), release.getMinorRelease(), release.getIteration(), release.getPatch(), release.getRevision()); } } /** * Computes the release full number from release fragments * * @param major major release number * @param minor minor release number * @param iteration iteration number * @param patch patch number * @param revision revision number * @return the full integer value of this release */ public static long computeVersion(int major, int minor, int iteration, int patch, int revision) { return Math.max(revision, 0) * REVISION + patch * PATCH + iteration * ITERATION + minor * MINOR + major * MAJOR; } /** * Retrieves the release type from the specified version information by checking the release * increment between the previous version of the version hierarchy and the current one. * * @param release release to retrieve the type of * @return the release type */ public static long getReleaseType(IVersionInfo release) { IVersionInfo previousRelease = release.getPreviousVersion(); // If this is our first release, it is a major one if (previousRelease == null) { return MAJOR; } else { if (release.getMajorRelease() > previousRelease.getMajorRelease()) { return MAJOR; } else if (release.getMinorRelease() > previousRelease.getMinorRelease()) { return MINOR; } else if (release.getIteration() > previousRelease.getIteration()) { return ITERATION; } else if (release.getPatch() > previousRelease.getPatch()) { return PATCH; } else { return 0; } } } /** * Sets the current version view * * @param view current version view * @deprecated please use {@link IWorkspaceService#setCurrentWorkspace(IWorkspace)} instead */ @Deprecated public static void setCurrentView(IWorkspace view) { VCSPlugin.getViewService().setCurrentWorkspace(view); } /** * Retrieves the current version view. * * @return the current version view * @deprecated please use {@link IWorkspaceService#getCurrentWorkspace()} instead */ @Deprecated public static IWorkspace getCurrentView() { return VCSPlugin.getViewService().getCurrentWorkspace(); } /** * Removes a versionable from its owning container. Dependency lookups should already have been * done before calling this method. This method will perform version checks before allowing the * versionable removal. * * @param v versionable to remove from owning container */ public static void removeVersionable(IVersionable<?> v) { IVersionContainer c = v.getContainer(); if (c != null) { // Ensuring the owning container is modifiable c = getVersioningService().ensureModifiable(c); // Removing the versionable if (!c.removeVersionable(v)) { throw new OutOfDateObjectException(c, VCSMessages.getString("helper.version.containerUpdated")); //$NON-NLS-1$ } // Unreferencing CorePlugin.getService(IReferenceManager.class).dereference(v); // Unreferencing inner references for (IReferenceable r : v.getReferenceMap().values()) { // Unreferencing replaced instance try { CorePlugin.getService(IReferenceManager.class).dereference(r); } catch (ErrorException e) { log.error(MessageFormat.format(VCSMessages.getString("helper.version.dereferenceError"), r //$NON-NLS-1$ .getReference().getUID().toString()), e); } } // Updating the parent container CorePlugin.getIdentifiableDao().save(c); log.info(MessageFormat.format(VCSMessages.getString("helper.version.versionableRemoved"), //$NON-NLS-1$ v.getType().getName(), v.getName())); } } /** * This method returns a collection of the commands needed to properly clear the hibernate * session without conflicting with any opened view. * * @param flushReferences a boolean indicating if the command should flush neXtep soft * references during the cleanup * @return a list of commands to execute */ public static List<ICommand> getClearSessionCommands(final boolean flushReferences) { List<ICommand> cmds = new ArrayList<ICommand>(); cmds.add(new ICommand() { @Override public Object execute(Object... parameters) { // We clear our hibernate session and reload our view HibernateUtil.getInstance().clearAllSessions(); // getSession().clear(); if (flushReferences) { CorePlugin.getService(IReferenceManager.class).flush(); } VersionBranch.reset(); Activity.reset(); return null; } @Override public String getName() { return VCSMessages.getString("helper.version.sessionCleanup"); //$NON-NLS-1$ } }); cmds.add(new ICommand() { @Override public Object execute(Object... parameters) { logInfo(VCSMessages.getString("loadRootBranch")); //$NON-NLS-1$ CorePlugin.getIdentifiableDao().load(VersionBranch.class, new UID(1)); // Reloading user IRepositoryUser user = (IRepositoryUser) CorePlugin.getIdentifiableDao().load(RepositoryUser.class, getCurrentUser().getUID()); setCurrentUser(user); // try { // //Sleeping only to ensure proper progress display // Thread.sleep(200); // } catch(InterruptedException e) { // // } return null; } @Override public String getName() { return VCSMessages.getString("loadRootBranch"); //$NON-NLS-1$ } }); return cmds; } /** * Relinks the given object into the current view. This method should be called when adding a * new non-empty object into the view after the view initialization. * * @param o object to relink to the view */ public static void relink(ITypedObject o) { MultiValueMap invRefMap = null; if (o instanceof IReferenceable) { // Retrieving dependency map of the element invRefMap = CorePlugin.getService(IReferenceManager.class) .getReverseDependenciesMapFor(((IReferenceable) o).getReference()); } else { // Backward compatibility invRefMap = CorePlugin.getService(IReferenceManager.class).getReverseDependenciesMap(); } relink(o, invRefMap); } /** * Relinks the given object into the current view. This method should be called when adding a * new non-empty object into the view after the view initialization.<br> * This method allow caller to pre-build the reverse dependencies map to use by linkers. * * @param o object to relink to the view * @param invRefMap reverse dependencies map */ public static void relink(ITypedObject o, MultiValueMap invRefMap) { Collection<IConfigurationElement> elts = Designer.getInstance() .getExtensions(IViewLinker.LINKER_EXTENSION_POINT_ID, "name", "*"); //$NON-NLS-1$ //$NON-NLS-2$ for (IConfigurationElement conf : elts) { try { IViewLinker linker = (IViewLinker) conf.createExecutableExtension("class"); //$NON-NLS-1$ try { linker.relink(o, invRefMap); } catch (Exception e) { log.error(MessageFormat.format(VCSMessages.getString("linkException"), //$NON-NLS-1$ conf.getAttribute("name")), e); //$NON-NLS-1$ } } catch (CoreException e) { final String confName = conf.getAttribute("name"); //$NON-NLS-1$ log.error(MessageFormat.format(VCSMessages.getString("helper.version.linkerLoadFail"), confName), //$NON-NLS-1$ e); } } } /** * Logs information messages. Should a progress monitor be currently active (typically during * the splash screen), messages will be sent as the task name of the monitor. * * @param message message to log */ private static void logInfo(String message) { log.info(message); IProgressMonitor m = Designer.getProgressMonitor(); if (m != null) { m.setTaskName(message); } } /** * @return the current repository user * @deprecated please use {@link IWorkspaceService#getCurrentUser()} instead */ @Deprecated public static IRepositoryUser getCurrentUser() { return VCSPlugin.getViewService().getCurrentUser(); } /** * Defines the currently connected repository user * * @param user connected user * @deprecated please use {@link IWorkspaceService#setCurrentUser(IRepositoryUser)} instead */ @Deprecated public static void setCurrentUser(IRepositoryUser user) { VCSPlugin.getViewService().setCurrentUser(user); } /** * This method retrieves the real revision number of the specified versionable by querying the * database's current value and compares it with the previously loaded versionable revision * number.<br> * The revision is used to determine if the current workspace object is up to date or if it has * been modified externally and therefore needs to be synchronized. * * @param v versionable to retrieve revision * @return <code>true</code> if the versionable is up to date, <code>false</code> if it needs to * be synchronized. */ public static boolean isUpToDate(IVersionable<?> v) { if (v == null || v.getVersion() == null || v.getVersion().getUID() == null) { return true; } return queryIsUpToDate("SELECT rv.update_revision " //$NON-NLS-1$ + "FROM {h-schema}rep_versions rv " //$NON-NLS-1$ + "WHERE rv.version_id = ?", //$NON-NLS-1$ v.getVersion().getUID().rawId(), v.getVersion().getUpdateRevision()); } /** * Executes the specified revision-check query for the given element id. The return boolean * indicates whether the revision number fetched from the database through the given query * matches the expected revision number. * * @param query SQL query which can retrieve the revision number from its ID * @param id unique ID of element to check the revision * @param expectedRevision expected revision number * @return <code>true</code> if the expected revision number matches the repository revision * number, else <code>false</code> */ private static boolean queryIsUpToDate(String query, long id, long expectedRevision) { final Session session = HibernateUtil.getInstance().getSandBoxSession(); session.flush(); session.clear(); SQLQuery sqlQuery = session.createSQLQuery(query); sqlQuery.setLong(0, id); Number revision = (Number) sqlQuery.uniqueResult(); return revision == null || (revision != null && revision.longValue() == expectedRevision); } /** * Same as {@link VersionHelper#isUpToDate(IVersionable)} except that this method can handle * views (which are not versionables * * @param c container to check * @return <code>true</code> if synched with repository, else <code>false</code> */ public static boolean isContainerUpToDate(IVersionContainer c) { if (c == null || c.getUID() == null) { return true; } if (c instanceof IVersionable<?>) { return isUpToDate((IVersionable<?>) c); } else if (c instanceof IWorkspace) { return queryIsUpToDate("SELECT vv.update_revision " //$NON-NLS-1$ + "FROM {h-schema}rep_version_views vv " //$NON-NLS-1$ + "WHERE vv.view_id = ? ", c.getUID().rawId(), ((IWorkspace) c).getRevision()); //$NON-NLS-1$ } return true; } /** * Refreshes the specified versionable from the database current information * * @param v versionable to refresh */ public static IVersionable<?> refresh(IVersionable<?> v) { CorePlugin.getIdentifiableDao().refresh(v.getVersion()); CorePlugin.getIdentifiableDao().refresh(v); return v; } public static IVersionContainer refreshContainer(IVersionContainer c) { if (c instanceof IVersionable<?>) { return (IVersionContainer) refresh((IVersionable<?>) c); } else { return null; } } public static IVersioningService getVersioningService() { return VCSPlugin.getService(IVersioningService.class); } /** * Checks if the specified version is available. This method will query the database to check * that the specified version number has not already been created (for example on another view) * * @param version version to check the availability of * @return a flag indicating the version number availability */ @SuppressWarnings("unchecked") public static boolean isVersionAvailable(IVersionInfo version) { final long versionNo = computeVersion(version); List<IVersionInfo> versions = (List<IVersionInfo>) CorePlugin.getIdentifiableDao() .loadForeignKey(VersionInfo.class, version.getReference().getReferenceId(), "reference"); //$NON-NLS-1$ // Checking availability of this version for (IVersionInfo info : new ArrayList<IVersionInfo>(versions)) { if (info.isDropped()) { versions.remove(info); } else { // Comparing values if (versionNo == computeVersion(info)) { return false; } } } return true; } /** * Informs whether the specified version has the eligible version in any of its previous * versions * * @param version version whose tree will be checked for predecessor * @param eligiblePredecessor the version which might be a predecessor * @return <code>true</code> if eligiblePredecessor is a predecessor of version, in other words * whenever it belongs to the previous / merge version tree of version */ public static boolean isPredecessor(IVersionInfo version, IVersionInfo eligiblePredecessor) { // Recursivity stop check if (version == null) { return false; } else if (version.equals(eligiblePredecessor)) { return true; } else { return isPredecessor(version.getPreviousVersion(), eligiblePredecessor) || isPredecessor(version.getMergedFromVersion(), eligiblePredecessor); } } }