Allow updating the parent project from the WebUI

This change adds support for updating the parent project of a child
project from the WebUI. This functionality is offered in the
ProjectAccessScreen. When the access rights are changed the user is
also able to change the parent project (if the user has the needed
privileges, means if he is administrator). When the user saves the
new settings only one RPC to the server is done that updates both the
access rights and the parent project property. Technically both
updates are modifications of the 'project.config' file in the
'refs/meta/config' branch. There will be only one commit for this
file that does both updates.

In the UI only valid parent projects are suggested as new parent
project (all projects that would cause a cycle in the line of parent
projects are not suggested).

Bug: issue 1298
Change-Id: Ic63bdb039ea5057a0551138f8fef9ede280b2be3
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-10-16 23:33:48 +02:00
parent f538e918a6
commit b0b0bbba71
16 changed files with 288 additions and 56 deletions

View File

@@ -28,6 +28,7 @@ public class ProjectAccess {
protected Set<String> ownerOf;
protected boolean isConfigVisible;
protected boolean canUpload;
protected boolean canChangeParent;
protected LabelTypes labelTypes;
protected Map<String, String> capabilities;
@@ -107,6 +108,14 @@ public class ProjectAccess {
this.canUpload = canUpload;
}
public boolean canChangeParent() {
return canChangeParent;
}
public void setCanChangeParent(boolean canChangeParent) {
this.canChangeParent = canChangeParent;
}
public LabelTypes getLabelTypes() {
return labelTypes;
}

View File

@@ -33,11 +33,11 @@ public interface ProjectAdminService extends RemoteJsonService {
@Audit
@SignInRequired
void changeProjectAccess(Project.NameKey projectName, String baseRevision,
String message, List<AccessSection> sections,
String message, List<AccessSection> sections, Project.NameKey parentProjectName,
AsyncCallback<ProjectAccess> callback);
@SignInRequired
void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
String message, List<AccessSection> sections,
String message, List<AccessSection> sections, Project.NameKey parentProjectName,
AsyncCallback<Change.Id> callback);
}

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common.errors;
/** Error indicating that updating a parent project failed. */
public class UpdateParentFailedException extends Exception {
private static final long serialVersionUID = 1L;
public static final String MESSAGE = "Update Parent Project Failed: ";
public UpdateParentFailedException(final String message,
final Throwable why) {
super(MESSAGE + ": " + message, why);
}
}

View File

@@ -34,4 +34,6 @@ public interface GerritMessages extends Messages {
String pluginFailed(String scriptPath);
String cannotDownloadPlugin(String scriptPath);
String parentUpdateFailed(String message);
}

View File

@@ -15,3 +15,5 @@ branchCreationConflict = Cannot create branch {0} since it conflicts with branch
pluginFailed = Plugin JavaScript {0} failed to load
cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}.
parentUpdateFailed = Setting parent project failed: {0}

View File

@@ -18,6 +18,7 @@ import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.GitwebLink;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.ParentProjectBox;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.client.Branch;
@@ -55,6 +56,10 @@ public class ProjectAccessEditor extends Composite implements
@UiField
Hyperlink parentProject;
@UiField
@Editor.Ignore
ParentProjectBox parentProjectBox;
@UiField
DivElement history;
@@ -106,6 +111,11 @@ public class ProjectAccessEditor extends Composite implements
parentProject.setText(parent.get());
parentProject.setTargetHistoryToken( //
Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS));
parentProjectBox.setVisible(editing && value.canChangeParent());
parentProjectBox.setProject(value.getProjectName());
parentProjectBox.setParentProject(value.getInheritsFrom());
parentProject.setVisible(!parentProjectBox.isVisible());
} else {
inheritsFrom.getStyle().setDisplay(Display.NONE);
}
@@ -135,6 +145,7 @@ public class ProjectAccessEditor extends Composite implements
}
}
value.setLocal(keep);
value.setInheritsFrom(parentProjectBox.getParentProjectName());
}
@Override

View File

@@ -56,6 +56,9 @@ limitations under the License.
<div ui:field='inheritsFrom' class='{style.inheritsFrom}'>
<span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span>
<q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/>
<q:ParentProjectBox
ui:field='parentProjectBox'
visible='false'/>
</div>
<div ui:field='history' class='{style.history}'>
<span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span>

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.client.admin;
import static com.google.gerrit.common.ProjectAccessUtil.mergeSections;
import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.config.CapabilityInfo;
import com.google.gerrit.client.config.ConfigServerApi;
@@ -28,6 +29,7 @@ import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.errors.UpdateParentFailedException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
@@ -45,6 +47,7 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtjsonrpc.client.RemoteJsonException;
import java.util.Collections;
import java.util.HashMap;
@@ -205,6 +208,7 @@ public class ProjectAccessScreen extends ProjectScreen {
access.getRevision(), //
message, //
access.getLocal(), //
access.getInheritsFrom(), //
new GerritCallback<ProjectAccess>() {
@Override
public void onSuccess(ProjectAccess newAccess) {
@@ -250,8 +254,16 @@ public class ProjectAccessScreen extends ProjectScreen {
public void onFailure(Throwable caught) {
error.clear();
enable(true);
if (caught instanceof RemoteJsonException
&& caught.getMessage().startsWith(
UpdateParentFailedException.MESSAGE)) {
new ErrorDialog(Gerrit.M.parentUpdateFailed(caught.getMessage()
.substring(UpdateParentFailedException.MESSAGE.length() + 1)))
.center();
} else {
super.onFailure(caught);
}
}
});
}
@@ -275,6 +287,7 @@ public class ProjectAccessScreen extends ProjectScreen {
access.getRevision(), //
message, //
access.getLocal(), //
access.getInheritsFrom(), //
new GerritCallback<Change.Id>() {
@Override
public void onSuccess(Change.Id changeId) {

View File

@@ -110,6 +110,15 @@ public class ProjectApi {
});
}
public static void getChildren(Project.NameKey name, boolean recursive,
AsyncCallback<JsArray<ProjectInfo>> cb) {
RestApi view = project(name).view("children");
if (recursive) {
view.addParameterTrue("recursive");
}
view.get(cb);
}
public static void getDescription(Project.NameKey name,
AsyncCallback<NativeString> cb) {
project(name).view("description").get(cb);

View File

@@ -0,0 +1,101 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.ui;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.projects.ProjectInfo;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import java.util.HashSet;
import java.util.Set;
public class ParentProjectBox extends Composite {
private final NpTextBox textBox;
private final SuggestBox suggestBox;
private final ParentProjectNameSuggestOracle suggestOracle;
public ParentProjectBox() {
textBox = new NpTextBox();
suggestOracle = new ParentProjectNameSuggestOracle();
suggestBox = new SuggestBox(suggestOracle, textBox);
initWidget(suggestBox);
}
public void setVisibleLength(int len) {
textBox.setVisibleLength(len);
}
public void setProject(final Project.NameKey project) {
suggestOracle.setProject(project);
}
public void setParentProject(final Project.NameKey parent) {
suggestBox.setText(parent != null ? parent.get() : "");
}
public Project.NameKey getParentProjectName() {
final String projectName = suggestBox.getText().trim();
if (projectName.isEmpty()) {
return null;
}
return new Project.NameKey(projectName);
}
private static class ParentProjectNameSuggestOracle extends ProjectNameSuggestOracle {
private Set<String> exclude = new HashSet<String>();
public void setProject(Project.NameKey project) {
exclude.clear();
exclude.add(project.get());
ProjectApi.getChildren(project, true, new AsyncCallback<JsArray<ProjectInfo>>() {
@Override
public void onSuccess(JsArray<ProjectInfo> result) {
for (ProjectInfo p : Natives.asList(result)) {
exclude.add(p.name());
}
}
@Override
public void onFailure(Throwable caught) {
}
});
}
@Override
public void _onRequestSuggestions(Request req, final Callback callback) {
super._onRequestSuggestions(req, new Callback() {
public void onSuggestionsReady(Request request, Response response) {
if (exclude.size() > 0) {
Set<Suggestion> filteredSuggestions =
new HashSet<Suggestion>(response.getSuggestions());
for (Suggestion s : response.getSuggestions()) {
if (exclude.contains(s.getReplacementString())) {
filteredSuggestions.remove(s);
}
}
response.setSuggestions(filteredSuggestions);
}
callback.onSuggestionsReady(request, response);
}
});
}
}
}

View File

@@ -19,12 +19,15 @@ import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SetParent;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -35,9 +38,11 @@ import java.util.List;
class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
interface Factory {
ChangeProjectAccess create(@Assisted Project.NameKey projectName,
ChangeProjectAccess create(
@Assisted("projectName") Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
@Nullable @Assisted String message);
}
@@ -45,17 +50,21 @@ class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
private final ProjectCache projectCache;
@Inject
ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final GroupBackend groupBackend,
final MetaDataUpdate.User metaDataUpdateFactory,
ChangeProjectAccess(ProjectAccessFactory.Factory projectAccessFactory,
ProjectControl.Factory projectControlFactory,
ProjectCache projectCache, GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory,
AllProjectsNameProvider allProjects,
Provider<SetParent> setParent,
@Assisted final Project.NameKey projectName,
@Nullable @Assisted final ObjectId base,
@Assisted("projectName") Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
@Nullable @Assisted String message) {
super(projectControlFactory, groupBackend, metaDataUpdateFactory,
projectName, base, sectionList, message, true);
allProjects, setParent, projectName, base, sectionList,
parentProjectName, message, true);
this.projectAccessFactory = projectAccessFactory;
this.projectCache = projectCache;
}

View File

@@ -203,6 +203,8 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
detail.setOwnerOf(ownerOf);
detail.setCanUpload(pc.isOwner()
|| (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
detail.setCanChangeParent(pc.getCurrentUser().getCapabilities()
.canAdministrateServer());
detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
detail.setLabelTypes(pc.getLabelTypes());
return detail;

View File

@@ -22,16 +22,23 @@ import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.common.errors.UpdateParentFailedException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.project.SetParent;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -47,27 +54,32 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> {
private final ProjectControl.Factory projectControlFactory;
protected final GroupBackend groupBackend;
private final MetaDataUpdate.User metaDataUpdateFactory;
private final AllProjectsNameProvider allProjects;
private final Provider<SetParent> setParent;
protected final Project.NameKey projectName;
protected final ObjectId base;
private List<AccessSection> sectionList;
private final Project.NameKey parentProjectName;
protected String message;
private boolean checkIfOwner;
protected ProjectAccessHandler(
final ProjectControl.Factory projectControlFactory,
final GroupBackend groupBackend,
final MetaDataUpdate.User metaDataUpdateFactory,
final Project.NameKey projectName, final ObjectId base,
final List<AccessSection> sectionList, final String message,
final boolean checkIfOwner) {
protected ProjectAccessHandler(ProjectControl.Factory projectControlFactory,
GroupBackend groupBackend, MetaDataUpdate.User metaDataUpdateFactory,
AllProjectsNameProvider allProjects, Provider<SetParent> setParent,
Project.NameKey projectName, ObjectId base,
List<AccessSection> sectionList, Project.NameKey parentProjectName,
String message, boolean checkIfOwner) {
this.projectControlFactory = projectControlFactory;
this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allProjects = allProjects;
this.setParent = setParent;
this.projectName = projectName;
this.base = base;
this.sectionList = sectionList;
this.parentProjectName = parentProjectName;
this.message = message;
this.checkIfOwner = checkIfOwner;
}
@@ -75,7 +87,7 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> {
@Override
public final T call() throws NoSuchProjectException, IOException,
ConfigInvalidException, InvalidNameException, NoSuchGroupException,
OrmException {
OrmException, UpdateParentFailedException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
@@ -120,6 +132,20 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> {
}
}
if (!config.getProject().getNameKey().equals(allProjects.get()) &&
!config.getProject().getParent(allProjects.get()).equals(parentProjectName)) {
try {
setParent.get().validateParentUpdate(projectControl, parentProjectName.get());
} catch (AuthException e) {
throw new UpdateParentFailedException(e.getMessage(), e);
} catch (ResourceConflictException e) {
throw new UpdateParentFailedException(e.getMessage(), e);
} catch (UnprocessableEntityException e) {
throw new UpdateParentFailedException(e.getMessage(), e);
}
config.getProject().setParentName(parentProjectName);
}
if (message != null && !message.isEmpty()) {
if (!message.endsWith("\n")) {
message += "\n";

View File

@@ -56,14 +56,16 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
@Override
public void changeProjectAccess(Project.NameKey projectName,
String baseRevision, String msg, List<AccessSection> sections,
AsyncCallback<ProjectAccess> cb) {
changeProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
Project.NameKey parentProjectName, AsyncCallback<ProjectAccess> cb) {
changeProjectAccessFactory.create(projectName, getBase(baseRevision),
sections, parentProjectName, msg).to(cb);
}
@Override
public void reviewProjectAccess(Project.NameKey projectName,
String baseRevision, String msg, List<AccessSection> sections,
AsyncCallback<Change.Id> cb) {
reviewProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
Project.NameKey parentProjectName, AsyncCallback<Change.Id> cb) {
reviewProjectAccessFactory.create(projectName, getBase(baseRevision),
sections, parentProjectName, msg).to(cb);
}
}

View File

@@ -31,6 +31,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
@@ -39,6 +40,7 @@ import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SetParent;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -60,9 +62,11 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
LoggerFactory.getLogger(ReviewProjectAccess.class);
interface Factory {
ReviewProjectAccess create(@Assisted Project.NameKey projectName,
ReviewProjectAccess create(
@Assisted("projectName") Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
@Nullable @Assisted String message);
}
@@ -84,13 +88,17 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
ChangeControl.GenericFactory changeFactory,
ChangeIndexer indexer, ChangeHooks hooks,
CreateChangeSender.Factory createChangeSenderFactory,
AllProjectsNameProvider allProjects,
Provider<SetParent> setParent,
@Assisted Project.NameKey projectName,
@Assisted("projectName") Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
@Nullable @Assisted String message) {
super(projectControlFactory, groupBackend, metaDataUpdateFactory,
projectName, base, sectionList, message, false);
allProjects, setParent, projectName, base, sectionList,
parentProjectName, message, false);
this.db = db;
this.user = user;
this.patchSetInfoFactory = patchSetInfoFactory;

View File

@@ -38,7 +38,7 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
import java.io.IOException;
class SetParent implements RestModifyView<ProjectResource, Input> {
public class SetParent implements RestModifyView<ProjectResource, Input> {
static class Input {
@DefaultInput
String parent;
@@ -63,41 +63,14 @@ class SetParent implements RestModifyView<ProjectResource, Input> {
BadRequestException, ResourceConflictException,
ResourceNotFoundException, UnprocessableEntityException, IOException {
ProjectControl ctl = rsrc.getControl();
validateParentUpdate(ctl, input.parent);
IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
if (!user.getCapabilities().canAdministrateServer()) {
throw new AuthException("not administrator");
}
if (rsrc.getNameKey().equals(allProjects)) {
throw new ResourceConflictException("cannot set parent of "
+ allProjects.get());
}
input.parent = Strings.emptyToNull(input.parent);
if (input.parent != null) {
ProjectState parent = cache.get(new Project.NameKey(input.parent));
if (parent == null) {
throw new UnprocessableEntityException("parent project " + input.parent
+ " not found");
}
if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
@Override
public boolean apply(ProjectState input) {
return input.getProject().getNameKey().equals(rsrc.getNameKey());
}
}).isPresent()) {
throw new ResourceConflictException("cycle exists between "
+ rsrc.getName() + " and " + parent.getProject().getName());
}
}
try {
MetaDataUpdate md = updateFactory.create(rsrc.getNameKey());
try {
ProjectConfig config = ProjectConfig.read(md);
Project project = config.getProject();
project.setParentName(input.parent);
project.setParentName(Strings.emptyToNull(input.parent));
String msg = Strings.emptyToNull(input.commitMessage);
if (msg == null) {
@@ -124,4 +97,39 @@ class SetParent implements RestModifyView<ProjectResource, Input> {
"invalid project.config: %s", e.getMessage()));
}
}
public void validateParentUpdate(final ProjectControl ctl, String newParent)
throws AuthException, ResourceConflictException,
UnprocessableEntityException {
IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
if (!user.getCapabilities().canAdministrateServer()) {
throw new AuthException("not administrator");
}
if (ctl.getProject().getNameKey().equals(allProjects)) {
throw new ResourceConflictException("cannot set parent of "
+ allProjects.get());
}
newParent = Strings.emptyToNull(newParent);
if (newParent != null) {
ProjectState parent = cache.get(new Project.NameKey(newParent));
if (parent == null) {
throw new UnprocessableEntityException("parent project " + newParent
+ " not found");
}
if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
@Override
public boolean apply(ProjectState input) {
return input.getProject().getNameKey()
.equals(ctl.getProject().getNameKey());
}
}).isPresent()) {
throw new ResourceConflictException("cycle exists between "
+ ctl.getProject().getName() + " and "
+ parent.getProject().getName());
}
}
}
}