mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Github style account url, project url, and pull/push url.
This commit is contained in:
parent
aa69531262
commit
0aab6f6089
@ -15,10 +15,9 @@
|
||||
<groupId>com.pmease</groupId>
|
||||
<artifactId>plugin.maven</artifactId>
|
||||
</plugin>
|
||||
<!-- Uncomment below if you'd like to publish an archetype based on your plugin -->
|
||||
<!--plugin>
|
||||
<artifactId>maven-archetype-plugin</artifactId>
|
||||
</plugin-->
|
||||
<!-- Uncomment below if you'd like to publish an archetype based on your
|
||||
plugin -->
|
||||
<!--plugin> <artifactId>maven-archetype-plugin</artifactId> </plugin -->
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
@ -34,11 +33,21 @@
|
||||
<artifactId>commons.loader</artifactId>
|
||||
<version>1.0.29</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>5.0.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>5.0.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
<artifactId>el-api</artifactId>
|
||||
<version>2.2.1-b04</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.web</groupId>
|
||||
<artifactId>el-impl</artifactId>
|
||||
<version>2.2.1-b05</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
||||
@ -25,5 +25,5 @@ public interface EditContext extends Serializable {
|
||||
Object renderForEdit(Object renderParam);
|
||||
|
||||
Object renderForView(Object renderParam);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -139,4 +139,12 @@ public class EditableUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static EditContext getContext(Serializable bean) {
|
||||
return AppLoader.getInstance(EditSupportRegistry.class).getBeanEditContext(bean);
|
||||
}
|
||||
|
||||
public static EditContext getContext(Serializable bean, String propertyName) {
|
||||
return AppLoader.getInstance(EditSupportRegistry.class).getPropertyEditContext(bean, propertyName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.pmease.commons.editable;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
import com.pmease.commons.editable.annotation.Name;
|
||||
import com.pmease.commons.util.StringUtils;
|
||||
|
||||
public class NameValidator implements ConstraintValidator<Name, String> {
|
||||
|
||||
// insert spaces in invalid chars in order to get a pretty display for
|
||||
// name validation error message. Refer to @Name annotation for more detail.
|
||||
public static final String invalidChars = ", / \\ : * ? \" < > | [ ]";
|
||||
|
||||
public void initialize(Name constaintAnnotation) {
|
||||
}
|
||||
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
|
||||
if (value == null)
|
||||
return true;
|
||||
|
||||
return StringUtils.containsNone(value, StringUtils.deleteWhitespace(invalidChars));
|
||||
}
|
||||
}
|
||||
@ -25,13 +25,13 @@ public class PropertyPath implements Serializable {
|
||||
public PropertyPath prepend(Serializable element) {
|
||||
PropertyPath newPath = new PropertyPath(elements);
|
||||
newPath.elements.add(0, element);
|
||||
return this;
|
||||
return newPath;
|
||||
}
|
||||
|
||||
public PropertyPath append(Serializable element) {
|
||||
PropertyPath newPath = new PropertyPath(elements);
|
||||
newPath.elements.add(element);
|
||||
return this;
|
||||
return newPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright PMEase Inc.,
|
||||
* Date: 2008-2-28
|
||||
* All rights reserved.
|
||||
*
|
||||
* Revision: $Id: PathElement.java 1209 2008-07-28 00:16:18Z robin $
|
||||
*/
|
||||
package com.pmease.commons.editable.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import com.pmease.commons.editable.NameValidator;
|
||||
|
||||
/**
|
||||
* @author robin
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@NotEmpty
|
||||
@Constraint(validatedBy=NameValidator.class)
|
||||
public @interface Name {
|
||||
String message() default "Name can not contain any of below characters:\n" +
|
||||
NameValidator.invalidChars;
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@ -75,15 +75,25 @@ public abstract class AbstractGenericDao<T extends AbstractEntity> implements Ge
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> query(Criterion[] criterions) {
|
||||
public List<T> query(Criterion... criterions) {
|
||||
return query(criterions, null, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T find(Criterion[] criterions) {
|
||||
public List<T> query() {
|
||||
return query(new Criterion[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T find(Criterion... criterions) {
|
||||
return find(criterions, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T find() {
|
||||
return find(new Criterion[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T find(Criterion[] criterions, Order[] orders) {
|
||||
|
||||
@ -76,7 +76,9 @@ public interface GenericDao<T extends AbstractEntity> {
|
||||
* @return
|
||||
* list of entity matching specified criterions.
|
||||
*/
|
||||
List<T> query(@Nullable Criterion[] criterions);
|
||||
List<T> query(Criterion... criterions);
|
||||
|
||||
List<T> query();
|
||||
|
||||
/**
|
||||
* This method expects to lookup a single entity with specified criteria.
|
||||
@ -87,7 +89,9 @@ public interface GenericDao<T extends AbstractEntity> {
|
||||
* @return
|
||||
* any matching entity. null if not found
|
||||
*/
|
||||
T find(@Nullable Criterion[] criterions);
|
||||
T find(Criterion... criterions);
|
||||
|
||||
T find();
|
||||
|
||||
/**
|
||||
* This method expects to find the first matching entity.
|
||||
|
||||
@ -5,6 +5,11 @@ import com.pmease.commons.loader.AbstractPluginModule;
|
||||
|
||||
public class JettyModule extends AbstractPluginModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends AbstractPlugin> getPluginClass() {
|
||||
return JettyPlugin.class;
|
||||
|
||||
@ -27,7 +27,7 @@ public class JettyPlugin extends AbstractPlugin {
|
||||
|
||||
private Server server;
|
||||
|
||||
private ServletContextHandler context;
|
||||
private ServletContextHandler contextHandler;
|
||||
|
||||
private final Set<ServerConfigurator> serverConfigurators;
|
||||
|
||||
@ -61,32 +61,36 @@ public class JettyPlugin extends AbstractPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public ServletContextHandler getContext() {
|
||||
return context;
|
||||
public ServletContextHandler getContextHandler() {
|
||||
return contextHandler;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
private Server createServer() {
|
||||
server = new Server();
|
||||
|
||||
context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
|
||||
context.setClassLoader(JettyPlugin.class.getClassLoader());
|
||||
contextHandler.setClassLoader(JettyPlugin.class.getClassLoader());
|
||||
|
||||
context.setErrorHandler(new ErrorPageErrorHandler());
|
||||
contextHandler.setErrorHandler(new ErrorPageErrorHandler());
|
||||
|
||||
context.addFilter(DisableTraceFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
contextHandler.addFilter(DisableTraceFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
for (ServletConfigurator configurator: servletContextConfigurators)
|
||||
configurator.configure(context);
|
||||
configurator.configure(contextHandler);
|
||||
|
||||
/*
|
||||
* Add Guice filter as last filter in order to make sure that filters and servlets
|
||||
* configured in Guice web module can be filtered correctly by filters added to
|
||||
* Jetty context directly.
|
||||
*/
|
||||
context.addFilter(GuiceFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
contextHandler.addFilter(GuiceFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
server.setHandler(context);
|
||||
server.setHandler(contextHandler);
|
||||
|
||||
for (ServerConfigurator configurator: serverConfigurators)
|
||||
configurator.configure(server);
|
||||
|
||||
@ -5,7 +5,7 @@ import org.apache.wicket.protocol.http.WicketServlet;
|
||||
|
||||
import com.pmease.commons.editable.EditSupport;
|
||||
import com.pmease.commons.loader.AbstractPluginModule;
|
||||
import com.pmease.commons.wicket.editable.EditHelper;
|
||||
import com.pmease.commons.wicket.editable.EditableResourceReference;
|
||||
|
||||
public class WicketModule extends AbstractPluginModule {
|
||||
|
||||
@ -16,7 +16,7 @@ public class WicketModule extends AbstractPluginModule {
|
||||
bind(WicketServlet.class).to(DefaultWicketServlet.class);
|
||||
bind(WicketFilter.class).to(DefaultWicketFilter.class);
|
||||
|
||||
contributeFromPackage(EditSupport.class, EditHelper.class);
|
||||
contributeFromPackage(EditSupport.class, EditableResourceReference.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ package com.pmease.commons.wicket.component.wizard;
|
||||
import org.apache.wicket.Component;
|
||||
|
||||
import com.pmease.commons.editable.EditContext;
|
||||
import com.pmease.commons.editable.EditableUtils;
|
||||
import com.pmease.commons.util.init.ManualConfig;
|
||||
import com.pmease.commons.wicket.editable.EditHelper;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class ManualConfigStep implements WizardStep {
|
||||
@ -15,12 +15,12 @@ public class ManualConfigStep implements WizardStep {
|
||||
|
||||
public ManualConfigStep(ManualConfig config) {
|
||||
this.config = config;
|
||||
editContext = EditHelper.getContext(config.getSetting());
|
||||
editContext = EditableUtils.getContext(config.getSetting());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component render(String componentId) {
|
||||
return EditHelper.renderForEdit(editContext, componentId);
|
||||
return (Component) editContext.renderForEdit(componentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
package com.pmease.commons.wicket.editable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
|
||||
import com.pmease.commons.editable.EditContext;
|
||||
import com.pmease.commons.editable.EditSupportRegistry;
|
||||
import com.pmease.commons.loader.AppLoader;
|
||||
|
||||
public class EditHelper {
|
||||
|
||||
public static EditContext getContext(Serializable bean) {
|
||||
return AppLoader.getInstance(EditSupportRegistry.class).getBeanEditContext(bean);
|
||||
}
|
||||
|
||||
public static EditContext getContext(Serializable bean, String propertyName) {
|
||||
return AppLoader.getInstance(EditSupportRegistry.class).getPropertyEditContext(bean, propertyName);
|
||||
}
|
||||
|
||||
public static Component renderForEdit(EditContext editContext, String componentId) {
|
||||
return (Component) editContext.renderForEdit(componentId);
|
||||
}
|
||||
|
||||
public static Component renderForView(EditContext editContext, String componentId) {
|
||||
return (Component) editContext.renderForView(componentId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,9 +2,11 @@ package com.pmease.gitop.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.cfg.NamingStrategy;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.pmease.commons.hibernate.AbstractEntity;
|
||||
import com.pmease.commons.hibernate.ModelProvider;
|
||||
import com.pmease.commons.hibernate.PrefixedNamingStrategy;
|
||||
@ -15,6 +17,8 @@ import com.pmease.commons.shiro.AbstractRealm;
|
||||
import com.pmease.commons.util.ClassUtils;
|
||||
import com.pmease.gitop.core.model.ModelLocator;
|
||||
import com.pmease.gitop.core.permission.UserRealm;
|
||||
import com.pmease.gitop.core.validation.ProjectNameReservation;
|
||||
import com.pmease.gitop.core.validation.UserNameReservation;
|
||||
|
||||
/**
|
||||
* NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class.
|
||||
@ -43,6 +47,28 @@ public class CoreModule extends AbstractPluginModule {
|
||||
});
|
||||
|
||||
contribute(ServletConfigurator.class, CoreServletConfigurator.class);
|
||||
|
||||
/*
|
||||
* Contribute empty reservations to avoid Guice complain
|
||||
*/
|
||||
contribute(UserNameReservation.class, new UserNameReservation() {
|
||||
|
||||
@Override
|
||||
public Set<String> getReserved() {
|
||||
return Sets.newHashSet();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Contribute empty reservations to avoid Guice complain
|
||||
*/
|
||||
contribute(ProjectNameReservation.class, new ProjectNameReservation() {
|
||||
|
||||
@Override
|
||||
public Set<String> getReserved() {
|
||||
return Sets.newHashSet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -54,6 +54,9 @@ public class GitFilter implements Filter {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (projectName.endsWith(".git"))
|
||||
projectName = projectName.substring(0, projectName.length()-".git".length());
|
||||
|
||||
Project project = projectManager.find(ownerName, projectName);
|
||||
if (project == null) {
|
||||
String message = "Unable to find project '" + projectName + "' owned by '" + ownerName + "'.";
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
package com.pmease.gitop.core.manager;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.inject.ImplementedBy;
|
||||
import com.pmease.commons.hibernate.dao.GenericDao;
|
||||
import com.pmease.gitop.core.manager.impl.DefaultProjectManager;
|
||||
import com.pmease.gitop.core.model.Project;
|
||||
import com.pmease.gitop.core.model.User;
|
||||
import com.pmease.gitop.core.storage.ProjectStorage;
|
||||
|
||||
@ImplementedBy(DefaultProjectManager.class)
|
||||
@ -13,7 +17,11 @@ public interface ProjectManager extends GenericDao<Project> {
|
||||
|
||||
ProjectStorage locateStorage(Project project);
|
||||
|
||||
Project find(String ownerName, String projectName);
|
||||
@Nullable Project find(String ownerName, String projectName);
|
||||
|
||||
@Nullable Project find(User owner, String projectName);
|
||||
|
||||
Set<String> getReservedNames();
|
||||
|
||||
Collection<Project> findPublic();
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
package com.pmease.gitop.core.manager;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.inject.ImplementedBy;
|
||||
import com.pmease.commons.hibernate.dao.GenericDao;
|
||||
import com.pmease.commons.util.namedentity.EntityLoader;
|
||||
@ -24,7 +28,9 @@ public interface UserManager extends GenericDao<User> {
|
||||
* @return
|
||||
* matching user, or <tt>null</tt> if not found
|
||||
*/
|
||||
User find(String userName);
|
||||
@Nullable User find(String userName);
|
||||
|
||||
Set<String> getReservedNames();
|
||||
|
||||
EntityLoader asEntityLoader();
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ package com.pmease.gitop.core.manager.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@ -11,24 +13,31 @@ import org.hibernate.criterion.Criterion;
|
||||
import org.hibernate.criterion.Restrictions;
|
||||
|
||||
import com.pmease.commons.git.Git;
|
||||
import com.pmease.commons.hibernate.Sessional;
|
||||
import com.pmease.commons.hibernate.Transactional;
|
||||
import com.pmease.commons.hibernate.dao.AbstractGenericDao;
|
||||
import com.pmease.commons.hibernate.dao.GeneralDao;
|
||||
import com.pmease.commons.util.FileUtils;
|
||||
import com.pmease.gitop.core.manager.ConfigManager;
|
||||
import com.pmease.gitop.core.manager.ProjectManager;
|
||||
import com.pmease.gitop.core.model.Project;
|
||||
import com.pmease.gitop.core.model.User;
|
||||
import com.pmease.gitop.core.storage.ProjectStorage;
|
||||
import com.pmease.gitop.core.validation.ProjectNameReservation;
|
||||
|
||||
@Singleton
|
||||
public class DefaultProjectManager extends AbstractGenericDao<Project> implements ProjectManager {
|
||||
|
||||
private ConfigManager configManager;
|
||||
private final ConfigManager configManager;
|
||||
|
||||
private final Set<ProjectNameReservation> nameReservations;
|
||||
|
||||
@Inject
|
||||
public DefaultProjectManager(GeneralDao generalDao, ConfigManager configManager) {
|
||||
public DefaultProjectManager(GeneralDao generalDao, ConfigManager configManager, Set<ProjectNameReservation> nameReservations) {
|
||||
super(generalDao);
|
||||
|
||||
this.configManager = configManager;
|
||||
this.nameReservations = nameReservations;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -40,8 +49,15 @@ public class DefaultProjectManager extends AbstractGenericDao<Project> implement
|
||||
ProjectStorage storage = locateStorage(entity);
|
||||
storage.clean();
|
||||
|
||||
new Git(storage.ofCode()).init().bare(true).call();
|
||||
File codeDir = storage.ofCode();
|
||||
FileUtils.createDir(codeDir);
|
||||
new Git(codeDir).init().bare(true).call();
|
||||
} else {
|
||||
File codeDir = locateStorage(entity).ofCode();
|
||||
if (!codeDir.exists()) {
|
||||
FileUtils.createDir(codeDir);
|
||||
new Git(codeDir).init().bare(true).call();
|
||||
}
|
||||
super.save(entity);
|
||||
}
|
||||
}
|
||||
@ -64,6 +80,7 @@ public class DefaultProjectManager extends AbstractGenericDao<Project> implement
|
||||
return query(new Criterion[]{Restrictions.eq("publiclyAccessible", true)});
|
||||
}
|
||||
|
||||
@Sessional
|
||||
@Override
|
||||
public Project find(String ownerName, String projectName) {
|
||||
Criteria criteria = createCriteria();
|
||||
@ -75,4 +92,19 @@ public class DefaultProjectManager extends AbstractGenericDao<Project> implement
|
||||
return (Project) criteria.uniqueResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getReservedNames() {
|
||||
Set<String> reservedNames = new HashSet<String>();
|
||||
for (ProjectNameReservation each: nameReservations)
|
||||
reservedNames.addAll(each.getReserved());
|
||||
|
||||
return reservedNames;
|
||||
}
|
||||
|
||||
@Sessional
|
||||
@Override
|
||||
public Project find(User owner, String projectName) {
|
||||
return find(Restrictions.eq("owner.id", owner.getId()), Restrictions.eq("name", projectName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.pmease.gitop.core.manager.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@ -15,15 +18,20 @@ import com.pmease.commons.util.namedentity.EntityLoader;
|
||||
import com.pmease.commons.util.namedentity.NamedEntity;
|
||||
import com.pmease.gitop.core.manager.UserManager;
|
||||
import com.pmease.gitop.core.model.User;
|
||||
import com.pmease.gitop.core.validation.UserNameReservation;
|
||||
|
||||
@Singleton
|
||||
public class DefaultUserManager extends AbstractGenericDao<User> implements UserManager {
|
||||
|
||||
private volatile Long rootUserId;
|
||||
|
||||
private final Set<UserNameReservation> nameReservations;
|
||||
|
||||
@Inject
|
||||
public DefaultUserManager(GeneralDao generalDao) {
|
||||
public DefaultUserManager(GeneralDao generalDao, Set<UserNameReservation> nameReservations) {
|
||||
super(generalDao);
|
||||
|
||||
this.nameReservations = nameReservations;
|
||||
}
|
||||
|
||||
@Sessional
|
||||
@ -98,4 +106,13 @@ public class DefaultUserManager extends AbstractGenericDao<User> implements User
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getReservedNames() {
|
||||
Set<String> reservedNames = new HashSet<String>();
|
||||
for (UserNameReservation each: nameReservations)
|
||||
reservedNames.addAll(each.getReserved());
|
||||
|
||||
return reservedNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ public class Project extends AbstractEntity implements UserBelonging {
|
||||
|
||||
public Collection<User> findAuthorizedUsers(GeneralOperation operation) {
|
||||
Set<User> authorizedUsers = new HashSet<User>();
|
||||
for (User user: Gitop.getInstance(UserManager.class).query(null)) {
|
||||
for (User user: Gitop.getInstance(UserManager.class).query()) {
|
||||
if (user.asSubject().isPermitted(new ObjectPermission(this, operation)))
|
||||
authorizedUsers.add(user);
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import com.pmease.gitop.core.permission.ObjectPermission;
|
||||
import com.pmease.gitop.core.permission.object.ProtectedObject;
|
||||
import com.pmease.gitop.core.permission.object.UserBelonging;
|
||||
import com.pmease.gitop.core.permission.operation.GeneralOperation;
|
||||
import com.pmease.gitop.core.validation.UserName;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@Entity
|
||||
@ -64,7 +65,7 @@ public class User extends AbstractUser implements ProtectedObject {
|
||||
private Collection<VoteInvitation> voteVitations = new ArrayList<VoteInvitation>();
|
||||
|
||||
@Editable(order=100)
|
||||
@NotEmpty
|
||||
@UserName
|
||||
@Override
|
||||
public String getName() {
|
||||
return super.getName();
|
||||
@ -223,7 +224,7 @@ public class User extends AbstractUser implements ProtectedObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Project each: Gitop.getInstance(ProjectManager.class).query(null)) {
|
||||
for (Project each: Gitop.getInstance(ProjectManager.class).query()) {
|
||||
ObjectPermission projectPermission = new ObjectPermission(each, each.getDefaultAuthorizedOperation());
|
||||
if (projectPermission.implies(objectPermission))
|
||||
return true;
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.pmease.gitop.core.validation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
|
||||
import com.pmease.commons.editable.annotation.Name;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Name
|
||||
@Constraint(validatedBy=ProjectNameValidator.class)
|
||||
public @interface ProjectName {
|
||||
|
||||
String message() default "";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.pmease.gitop.core.validation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.pmease.commons.loader.ExtensionPoint;
|
||||
|
||||
@ExtensionPoint
|
||||
public interface ProjectNameReservation {
|
||||
Set<String> getReserved();
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.pmease.gitop.core.validation;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.ProjectManager;
|
||||
|
||||
public class ProjectNameValidator implements ConstraintValidator<ProjectName, String> {
|
||||
|
||||
public void initialize(ProjectName constaintAnnotation) {
|
||||
}
|
||||
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
|
||||
if (value == null)
|
||||
return true;
|
||||
|
||||
constraintContext.disableDefaultConstraintViolation();
|
||||
constraintContext.buildConstraintViolationWithTemplate(value + " is a reserved word.").addConstraintViolation();
|
||||
return !Gitop.getInstance(ProjectManager.class).getReservedNames().contains(value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.pmease.gitop.core.validation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
|
||||
import com.pmease.commons.editable.annotation.Name;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Name
|
||||
@Constraint(validatedBy=UserNameValidator.class)
|
||||
public @interface UserName {
|
||||
|
||||
String message() default "";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.pmease.gitop.core.validation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.pmease.commons.loader.ExtensionPoint;
|
||||
|
||||
@ExtensionPoint
|
||||
public interface UserNameReservation {
|
||||
Set<String> getReserved();
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.pmease.gitop.core.validation;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.UserManager;
|
||||
|
||||
public class UserNameValidator implements ConstraintValidator<UserName, String> {
|
||||
|
||||
public void initialize(UserName constaintAnnotation) {
|
||||
}
|
||||
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
|
||||
if (value == null)
|
||||
return true;
|
||||
|
||||
constraintContext.disableDefaultConstraintViolation();
|
||||
constraintContext.buildConstraintViolationWithTemplate(value + " is a reserved word.").addConstraintViolation();
|
||||
return !Gitop.getInstance(UserManager.class).getReservedNames().contains(value);
|
||||
}
|
||||
}
|
||||
@ -2,23 +2,32 @@ package com.pmease.gitop.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.wicket.Application;
|
||||
import org.apache.wicket.Page;
|
||||
import org.apache.wicket.Session;
|
||||
import org.apache.wicket.bean.validation.BeanValidationConfiguration;
|
||||
import org.apache.wicket.core.request.mapper.MountedMapper;
|
||||
import org.apache.wicket.request.IRequestMapper;
|
||||
import org.apache.wicket.request.Request;
|
||||
import org.apache.wicket.request.Response;
|
||||
import org.apache.wicket.request.Url;
|
||||
import org.apache.wicket.util.time.Duration;
|
||||
import org.apache.wicket.util.time.Time;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.pmease.commons.wicket.AbstractWicketConfig;
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.ProjectManager;
|
||||
import com.pmease.gitop.core.manager.UserManager;
|
||||
import com.pmease.gitop.web.assets.AssetLocator;
|
||||
import com.pmease.gitop.web.common.component.avatar.AvatarImageResource;
|
||||
import com.pmease.gitop.web.common.component.avatar.AvatarImageResourceReference;
|
||||
@ -30,6 +39,7 @@ import com.pmease.gitop.web.page.account.setting.profile.AccountProfilePage;
|
||||
import com.pmease.gitop.web.page.account.setting.repos.AccountReposPage;
|
||||
import com.pmease.gitop.web.page.home.HomePage;
|
||||
import com.pmease.gitop.web.page.init.ServerInitPage;
|
||||
import com.pmease.gitop.web.page.project.ProjectHomePage;
|
||||
import com.pmease.gitop.web.shiro.LoginPage;
|
||||
import com.pmease.gitop.web.shiro.LogoutPage;
|
||||
import com.pmease.gitop.web.shiro.ShiroWicketPlugin;
|
||||
@ -110,19 +120,60 @@ public class GitopWebApp extends AbstractWicketConfig {
|
||||
// account related pages
|
||||
// --------------------------------------------------------
|
||||
|
||||
// project dashboard
|
||||
mount(new MountedMapper("/${user}/${project}", ProjectHomePage.class) {
|
||||
|
||||
@Override
|
||||
protected boolean urlStartsWith(Url url, String... segments) {
|
||||
List<String> normalizedSegments = normalizeUrlSegments(url.getSegments());
|
||||
if (normalizedSegments.size() < 2)
|
||||
return false;
|
||||
String userName = normalizedSegments.get(0);
|
||||
if (Gitop.getInstance(UserManager.class).getReservedNames().contains(userName))
|
||||
return false;
|
||||
|
||||
String projectName = normalizedSegments.get(1);
|
||||
return !Gitop.getInstance(ProjectManager.class).getReservedNames().contains(projectName);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// account dashboard
|
||||
mountPage("account/${user}", AccountHomePage.class);
|
||||
mount(new MountedMapper("/${user}", AccountHomePage.class) {
|
||||
|
||||
@Override
|
||||
protected boolean urlStartsWith(Url url, String... segments) {
|
||||
List<String> normalizedSegments = normalizeUrlSegments(url.getSegments());
|
||||
if (normalizedSegments.size() < 1)
|
||||
return false;
|
||||
String userName = normalizedSegments.get(0);
|
||||
return !Gitop.getInstance(UserManager.class).getReservedNames().contains(userName);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// account settings
|
||||
mountPage("settings/profile", AccountProfilePage.class);
|
||||
mountPage("settings/password", AccountPasswordPage.class);
|
||||
mountPage("settings/permission", AccountPermissionPage.class);
|
||||
mountPage("settings/repos", AccountReposPage.class);
|
||||
|
||||
mountPage("/test", TestPage.class);
|
||||
|
||||
// repository pages
|
||||
// --------------------------------------------------------
|
||||
|
||||
}
|
||||
|
||||
private List<String> normalizeUrlSegments(List<String> segments) {
|
||||
List<String> normalized = new ArrayList<String>();
|
||||
for (String each: segments) {
|
||||
each = StringUtils.remove(each, '/');
|
||||
if (each.length() != 0)
|
||||
normalized.add(each);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private void mountResources() {
|
||||
getSharedResources().add(AvatarImageResourceReference.AVATAR_RESOURCE, new AvatarImageResource());
|
||||
@ -139,4 +190,8 @@ public class GitopWebApp extends AbstractWicketConfig {
|
||||
public boolean isPublicSignupEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Iterable<IRequestMapper> getRequestMappers() {
|
||||
return getRootRequestMapperAsCompound();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
<wicket:extend>
|
||||
</wicket:extend>
|
||||
@ -1,9 +1,10 @@
|
||||
package com.pmease.gitop.web;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
|
||||
import com.pmease.commons.editable.EditContext;
|
||||
import com.pmease.commons.wicket.editable.EditHelper;
|
||||
import com.pmease.commons.editable.EditableUtils;
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.ProjectManager;
|
||||
import com.pmease.gitop.core.manager.UserManager;
|
||||
@ -17,7 +18,7 @@ public class TestPage extends BasePage {
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
final EditContext editContext = EditHelper.getContext(new Project());
|
||||
final EditContext editContext = EditableUtils.getContext(new Project());
|
||||
|
||||
Form<?> form = new Form<Void>("form") {
|
||||
|
||||
@ -34,7 +35,7 @@ public class TestPage extends BasePage {
|
||||
|
||||
};
|
||||
|
||||
form.add(EditHelper.renderForEdit(editContext, "editor"));
|
||||
form.add((Component)editContext.renderForEdit("editor"));
|
||||
|
||||
add(form);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import javax.inject.Singleton;
|
||||
import com.pmease.commons.jetty.ServletConfigurator;
|
||||
import com.pmease.commons.loader.AbstractPluginModule;
|
||||
import com.pmease.commons.wicket.AbstractWicketConfig;
|
||||
import com.pmease.gitop.core.validation.UserNameReservation;
|
||||
import com.pmease.gitop.web.resource.RestResourceModule;
|
||||
|
||||
/**
|
||||
@ -22,6 +23,7 @@ public class WebModule extends AbstractPluginModule {
|
||||
bind(SitePaths.class).in(Singleton.class);
|
||||
|
||||
contribute(ServletConfigurator.class, WebServletConfigurator.class);
|
||||
contribute(UserNameReservation.class, WebUserNameReservation.class);
|
||||
|
||||
install(new RestResourceModule());
|
||||
}
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package com.pmease.gitop.web;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.wicket.core.request.mapper.MountedMapper;
|
||||
import org.apache.wicket.core.request.mapper.ResourceMapper;
|
||||
import org.apache.wicket.request.IRequestMapper;
|
||||
import org.eclipse.jetty.servlet.ServletMapping;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.pmease.commons.jetty.JettyPlugin;
|
||||
import com.pmease.commons.util.ReflectionUtils;
|
||||
import com.pmease.gitop.core.validation.UserNameReservation;
|
||||
|
||||
public class WebUserNameReservation implements UserNameReservation {
|
||||
|
||||
private final JettyPlugin jettyPlugin;
|
||||
|
||||
private final GitopWebApp webApp;
|
||||
|
||||
@Inject
|
||||
public WebUserNameReservation(JettyPlugin jettyPlugin, GitopWebApp webApp) {
|
||||
this.jettyPlugin = jettyPlugin;
|
||||
this.webApp = webApp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getReserved() {
|
||||
Set<String> reserved = new HashSet<String>();
|
||||
for (ServletMapping mapping: jettyPlugin.getContextHandler().getServletHandler().getServletMappings()) {
|
||||
for (String pathSpec: mapping.getPathSpecs()) {
|
||||
pathSpec = StringUtils.stripStart(pathSpec, "/");
|
||||
pathSpec = StringUtils.substringBefore(pathSpec, "/");
|
||||
if (pathSpec.trim().length() != 0)
|
||||
reserved.add(pathSpec.trim());
|
||||
}
|
||||
}
|
||||
|
||||
reserved.add("wicket");
|
||||
|
||||
for (IRequestMapper mapper: webApp.getRequestMappers()) {
|
||||
if (mapper instanceof MountedMapper || mapper instanceof ResourceMapper) {
|
||||
try {
|
||||
Field field = ReflectionUtils.findField(mapper.getClass(), "mountSegments");
|
||||
Preconditions.checkNotNull(field);
|
||||
field.setAccessible(true);
|
||||
String[] mountSegments = (String[]) field.get(mapper);
|
||||
if (mountSegments != null && mountSegments.length != 0 && mountSegments[0] != null)
|
||||
reserved.add(mountSegments[0]);
|
||||
} catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reserved;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
<html xmlns:wicket>
|
||||
<wicket:extend>
|
||||
<h1>Welcome, Account home</h1>
|
||||
<div wicket:id="accountName"></div>
|
||||
<a wicket:id="link">link</a>
|
||||
</wicket:extend>
|
||||
</html>
|
||||
@ -1,16 +1,79 @@
|
||||
package com.pmease.gitop.web.page.account;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import com.pmease.commons.util.GeneralException;
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.UserManager;
|
||||
import com.pmease.gitop.core.model.User;
|
||||
import com.pmease.gitop.web.page.AbstractLayoutPage;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@RequiresAuthentication
|
||||
public class AccountHomePage extends AbstractLayoutPage {
|
||||
|
||||
private final IModel<User> accountModel;
|
||||
|
||||
public AccountHomePage(PageParameters params) {
|
||||
String accountName = params.get("user").toString();
|
||||
|
||||
User account = Gitop.getInstance(UserManager.class).find(accountName);
|
||||
if (account == null)
|
||||
throw new GeneralException("Account %s does not exist!", accountName);
|
||||
|
||||
final Long accountId = account.getId();
|
||||
|
||||
accountModel = new LoadableDetachableModel<User>() {
|
||||
|
||||
@Override
|
||||
protected User load() {
|
||||
return Gitop.getInstance(UserManager.class).load(accountId);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new Label("accountName", getAccount().getName()));
|
||||
|
||||
add(new Link<Void>("link") {
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPageTitle() {
|
||||
return "Gitop";
|
||||
}
|
||||
|
||||
public User getAccount() {
|
||||
return accountModel.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachModels() {
|
||||
accountModel.detach();
|
||||
|
||||
super.detachModels();
|
||||
}
|
||||
|
||||
public static PageParameters paramsOf(User account) {
|
||||
PageParameters params = new PageParameters();
|
||||
params.set("user", account.getName());
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,5 +5,7 @@
|
||||
<h3>Upload Test</h3>
|
||||
<div wicket:id="upload"></div>
|
||||
</div>
|
||||
<a wicket:id="accountLink">account</a>
|
||||
<a wicket:id="projectLink">project</a>
|
||||
</wicket:extend>
|
||||
</html>
|
||||
@ -1,7 +1,14 @@
|
||||
package com.pmease.gitop.web.page.home;
|
||||
|
||||
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
|
||||
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.ProjectManager;
|
||||
import com.pmease.gitop.core.manager.UserManager;
|
||||
import com.pmease.gitop.web.common.component.fileupload.FileUploadBar;
|
||||
import com.pmease.gitop.web.page.AbstractLayoutPage;
|
||||
import com.pmease.gitop.web.page.account.AccountHomePage;
|
||||
import com.pmease.gitop.web.page.project.ProjectHomePage;
|
||||
|
||||
public class HomePage extends AbstractLayoutPage {
|
||||
|
||||
@ -17,5 +24,8 @@ public class HomePage extends AbstractLayoutPage {
|
||||
super.onInitialize();
|
||||
|
||||
add(new FileUploadBar("upload"));
|
||||
|
||||
add(new BookmarkablePageLink<>("accountLink", AccountHomePage.class, AccountHomePage.paramsOf(Gitop.getInstance(UserManager.class).getRootUser())));
|
||||
add(new BookmarkablePageLink<>("projectLink", ProjectHomePage.class, ProjectHomePage.paramsOf(Gitop.getInstance(ProjectManager.class).load(1L))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
<html xmlns:wicket>
|
||||
<wicket:extend>
|
||||
<h1>Welcome, Project Home</h1>
|
||||
<div wicket:id="accountName"></div>
|
||||
<div wicket:id="projectName"></div>
|
||||
|
||||
<a wicket:id="link">link</a>
|
||||
</wicket:extend>
|
||||
</html>
|
||||
@ -0,0 +1,84 @@
|
||||
package com.pmease.gitop.web.page.project;
|
||||
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import com.pmease.commons.util.GeneralException;
|
||||
import com.pmease.gitop.core.Gitop;
|
||||
import com.pmease.gitop.core.manager.ProjectManager;
|
||||
import com.pmease.gitop.core.model.Project;
|
||||
import com.pmease.gitop.web.page.AbstractLayoutPage;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class ProjectHomePage extends AbstractLayoutPage {
|
||||
|
||||
private final IModel<Project> projectModel;
|
||||
|
||||
@Override
|
||||
protected String getPageTitle() {
|
||||
return "Project Home";
|
||||
}
|
||||
|
||||
public ProjectHomePage(PageParameters params) {
|
||||
String userName = params.get("user").toString();
|
||||
String projectName = params.get("project").toString();
|
||||
|
||||
if (projectName.endsWith(".git"))
|
||||
projectName = projectName.substring(0, projectName.length() - ".git".length());
|
||||
|
||||
Project project = Gitop.getInstance(ProjectManager.class).find(userName, projectName);
|
||||
|
||||
if (project == null)
|
||||
throw new GeneralException("Can not find project %s under account %s.", projectName, userName);
|
||||
|
||||
final Long projectId = project.getId();
|
||||
|
||||
projectModel = new LoadableDetachableModel<Project>() {
|
||||
|
||||
@Override
|
||||
protected Project load() {
|
||||
return Gitop.getInstance(ProjectManager.class).load(projectId);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
add(new Link<Void>("link") {
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new Label("accountName", getProject().getOwner().getName()));
|
||||
add(new Label("projectName", getProject().getName()));
|
||||
}
|
||||
|
||||
public Project getProject() {
|
||||
return projectModel.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachModels() {
|
||||
projectModel.detach();
|
||||
super.detachModels();
|
||||
}
|
||||
|
||||
public static PageParameters paramsOf(Project project) {
|
||||
PageParameters params = new PageParameters();
|
||||
params.set("user", project.getOwner().getName());
|
||||
params.set("project", project.getName());
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user