Commit 92c26bbe authored by Francesco Chicchiricco's avatar Francesco Chicchiricco
Browse files

[CRV-17] Done

parent a17bc6cd
......@@ -49,6 +49,10 @@ public class ChoreographyResource implements ChoreographyApi {
private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(5);
static {
DEPLOYED.put("xx45671", "WP7");
}
@Context
private UriInfo uriInfo;
......@@ -140,7 +144,7 @@ public class ChoreographyResource implements ChoreographyApi {
EXECUTOR.schedule(new CompletionNotifier(
choreographyId,
null,
choreographyName,
ChoreographyOperation.DELETE,
"All good",
null),
......
......@@ -113,8 +113,8 @@ public class ChoreographyDirectoryPanel extends GroupDirectoryPanel {
public void onClick(final AjaxRequestTarget target) {
try {
choreographyRestClient.deleteChoreography(
model.getObject().getPlainAttrMap().get("id").getValues().iterator().next());
restClient.delete(model.getObject().getETagValue(), model.getObject().getKey());
model.getObject().getPlainAttrMap().get("id").getValues().iterator().next(),
"Default");
SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
target.add(container);
} catch (SyncopeClientException e) {
......
......@@ -24,6 +24,7 @@ import eu.chorevolution.idm.common.to.ChoreographyTO;
import eu.chorevolution.idm.common.to.CoordinationDelegateTO;
import eu.chorevolution.idm.common.to.ServiceTO;
import java.util.List;
import org.apache.syncope.common.rest.api.service.ChoreographyService;
import org.apache.syncope.common.rest.api.service.ConsoleInterfaceService;
public class ChoreographyRestClient extends BaseRestClient {
......@@ -86,8 +87,8 @@ public class ChoreographyRestClient extends BaseRestClient {
return getService(ConsoleInterfaceService.class).averageOperationList(choreographyId, cdName);
}
public void deleteChoreography(final String choreographyId) {
getService(ConsoleInterfaceService.class).deleteChoreography(choreographyId);
public void deleteChoreography(final String choreographyId, final String enactmentEngine) {
getService(ChoreographyService.class).delete(choreographyId, enactmentEngine);
}
public void deleteInstance(final String choreographyInstancePK) {
......
......@@ -21,7 +21,6 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import eu.chorevolution.chors.ChorSpecUtils;
import eu.chorevolution.datamodel.Choreography;
import eu.chorevolution.datamodel.ExistingService;
import eu.chorevolution.datamodel.ServiceGroup;
import eu.chorevolution.idm.common.ChorevolutionEntitlement;
import eu.chorevolution.idm.common.types.ChoreographyAction;
import eu.chorevolution.idm.common.types.ChoreographyOperation;
......@@ -51,6 +50,7 @@ import org.apache.syncope.common.lib.patch.AnyObjectPatch;
import org.apache.syncope.common.lib.patch.AttrPatch;
import org.apache.syncope.common.lib.patch.GroupPatch;
import org.apache.syncope.common.lib.patch.MembershipPatch;
import org.apache.syncope.common.lib.patch.StringReplacePatchItem;
import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.AnyTypeClassTO;
import org.apache.syncope.common.lib.to.AttrTO;
......@@ -60,9 +60,13 @@ import org.apache.syncope.common.lib.to.TypeExtensionTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.AttrSchemaType;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.SchemaType;
import org.apache.syncope.core.persistence.api.dao.ChoreographyInstanceDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.spring.security.Encryptor;
......@@ -73,6 +77,8 @@ import org.springframework.stereotype.Component;
@Component
public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
private static final int PAGE_SIZE = 100;
private static final String SERVICE_TYPE = "SERVICE";
private static final String ENACTMENT_ENGINE_TYPE = "ENACTMENT ENGINE";
......@@ -95,7 +101,7 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
props.load(propStream);
SECRET_KEY = props.getProperty("secretKey");
} catch (Exception e) {
} catch (IOException e) {
LOG.error("Could not read security parameters", e);
} finally {
org.apache.commons.io.IOUtils.closeQuietly(propStream);
......@@ -115,6 +121,9 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
@Autowired
private SchemaLogic schemaLogic;
@Autowired
private ChoreographyInstanceDAO choreographyInstanceDAO;
private WebClient getWebClient(final String enactmentEngineKey, final String endpoint) throws Exception {
AnyObjectTO enactmentEngine = anyObjectLogic.read(enactmentEngineKey);
if (!ENACTMENT_ENGINE_TYPE.equals(enactmentEngine.getType())) {
......@@ -191,16 +200,23 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
return generatedChoreographyId;
}
private void checkChoreographyExists(final String id) {
private GroupTO checkChoreographyExists(final String id) {
AttributeCond idCond = new AttributeCond(AttributeCond.Type.EQ);
idCond.setSchema(CHOREOGRAPHY_ID_SCHEMA);
idCond.setExpression(id);
List<GroupTO> candidates = groupLogic.search(
SearchCond.getLeafCond(idCond),
AttributeCond isChoreographyCond = new AttributeCond(AttributeCond.Type.EQ);
isChoreographyCond.setSchema("isChoreography");
isChoreographyCond.setExpression("true");
List<GroupTO> candidates = groupLogic.search(SearchCond.getAndCond(
SearchCond.getLeafCond(idCond), SearchCond.getLeafCond(isChoreographyCond)),
1, 1, Collections.<OrderByClause>emptyList(), SyncopeConstants.ROOT_REALM, false);
if (candidates.isEmpty()) {
throw new NotFoundException("Choreography " + id);
}
return candidates.get(0);
}
@PreAuthorize("hasRole('" + ChorevolutionEntitlement.CHOREOGRAPHY_UPDATE + "')")
......@@ -233,6 +249,77 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
}
}
private String getUSERTypeExtensionKey(final String name) {
return name + " USER Type Extension";
}
private void processChorSpec(
final Choreography chorSpec, final String choreographykey, final AnyTypeClassTO classForUserTE) {
chorSpec.getServiceGroups().forEach((serviceGroup) -> {
serviceGroup.getServices().stream().
filter((service) -> (service instanceof ExistingService)).forEachOrdered((service) -> {
try {
AnyObjectTO serviceTO = anyObjectLogic.read(service.getName());
if (!SERVICE_TYPE.equals(serviceTO.getType())) {
throw new NotFoundException(SERVICE_TYPE + " " + service.getName());
}
// add the relevant services to the group, according to the chorSpec,
// and save the security filter URL
AnyObjectPatch servicePatch = new AnyObjectPatch();
servicePatch.setKey(serviceTO.getKey());
MembershipPatch membershipPatch = new MembershipPatch.Builder().group(choreographykey).build();
String securityFilterURL = ChorSpecUtils.findSecurityFilterURL(chorSpec, service);
if (securityFilterURL != null) {
membershipPatch.getPlainAttrs().add(new AttrPatch.Builder().
attrTO(new AttrTO.Builder().
schema("Endpoint").value(securityFilterURL).build()).
build());
}
servicePatch.getMemberships().add(membershipPatch);
anyObjectLogic.update(servicePatch, false);
// for each service requiring per-user authentication, create the
// Username and Password schemas (if not found), for USER type extension
AttrTO serviceAuth = serviceTO.getPlainAttrMap().get("Service Authentication Type");
if (serviceAuth != null && !serviceAuth.getValues().isEmpty()
&& serviceAuth.getValues().get(0).equals("PER_USER")) {
PlainSchemaTO username = new PlainSchemaTO();
username.setKey(service.getName() + " Username");
try {
schemaLogic.read(SchemaType.PLAIN, username.getKey());
} catch (NotFoundException e) {
username.setType(AttrSchemaType.String);
username.setMandatoryCondition("true");
schemaLogic.create(SchemaType.PLAIN, username);
}
classForUserTE.getPlainSchemas().add(username.getKey());
PlainSchemaTO password = new PlainSchemaTO();
password.setKey(service.getName() + " Password");
try {
schemaLogic.read(SchemaType.PLAIN, password.getKey());
} catch (NotFoundException e) {
password.setType(AttrSchemaType.Encrypted);
password.setCipherAlgorithm(CipherAlgorithm.AES);
password.setSecretKey(SECRET_KEY);
password.setMandatoryCondition("true");
schemaLogic.create(SchemaType.PLAIN, password);
}
classForUserTE.getPlainSchemas().add(password.getKey());
}
} catch (NotFoundException e) {
LOG.error("Could not find service {} in the inventory, ignoring", service.getName(), e);
}
});
});
}
@PreAuthorize("hasRole('" + ChorevolutionEntitlement.NOTIFY_COMPLETION + "')")
public void notifyCompletion(
final String id,
......@@ -245,24 +332,27 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
operation, id, name, message);
byte[] chorSpecXML = null;
Choreography parsed = null;
if (enactedChorSpec != null) {
try {
chorSpecXML = IOUtils.readBytesFromStream(enactedChorSpec);
parsed = Choreography.fromXML(new ByteArrayInputStream(chorSpecXML));
} catch (IOException | JAXBException | XMLStreamException e) {
throw new BadRequestException("While reading enacted chorSpec for choreography " + id, e);
Choreography chorSpec = null;
if (operation != ChoreographyOperation.DELETE) {
if (enactedChorSpec != null) {
try {
chorSpecXML = IOUtils.readBytesFromStream(enactedChorSpec);
chorSpec = Choreography.fromXML(new ByteArrayInputStream(chorSpecXML));
} catch (IOException | JAXBException | XMLStreamException e) {
throw new BadRequestException("While reading enacted chorSpec for choreography " + id, e);
}
}
if (chorSpec == null) {
throw new BadRequestException("Cloud not parse enacted chorSpec for choreography " + id);
}
}
if (parsed == null) {
throw new BadRequestException("Cloud not parse enacted chorSpec for choreography " + id);
}
final Choreography chorSpec = parsed;
GroupTO choreography;
AnyTypeClassTO classForUserTE;
switch (operation) {
case CREATE:
// 1. create the choreography group
GroupTO choreography = new GroupTO();
choreography = new GroupTO();
choreography.setName(name);
choreography.setRealm(SyncopeConstants.ROOT_REALM);
choreography.getAuxClasses().add("Choreography");
......@@ -277,65 +367,12 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
choreography.getTypeExtensions().add(serviceTE);
choreography = groupLogic.create(choreography, false).getEntity();
final String key = choreography.getKey();
AnyTypeClassTO classForUserTE = new AnyTypeClassTO();
classForUserTE.setKey(name + " USER Type Extension");
// 2. process the chroSpec
classForUserTE = new AnyTypeClassTO();
classForUserTE.setKey(getUSERTypeExtensionKey(name));
for (ServiceGroup serviceGroup : chorSpec.getServiceGroups()) {
serviceGroup.getServices().stream().
filter((service) -> (service instanceof ExistingService)).forEachOrdered((service) -> {
try {
AnyObjectTO serviceTO = anyObjectLogic.read(service.getName());
if (!SERVICE_TYPE.equals(serviceTO.getType())) {
throw new NotFoundException(SERVICE_TYPE + " " + service.getName());
}
// 2. add the relevant services to the group, according to the chorSpec,
// and save the security filter URL
AnyObjectPatch servicePatch = new AnyObjectPatch();
servicePatch.setKey(serviceTO.getKey());
MembershipPatch membershipPatch = new MembershipPatch.Builder().group(key).build();
String securityFilterURL = ChorSpecUtils.findSecurityFilterURL(chorSpec, service);
if (securityFilterURL != null) {
membershipPatch.getPlainAttrs().add(new AttrPatch.Builder().
attrTO(new AttrTO.Builder().
schema("Endpoint").value(securityFilterURL).build()).
build());
}
servicePatch.getMemberships().add(membershipPatch);
anyObjectLogic.update(servicePatch, false);
// 3. for each service requiring per-user authentication, create the
// Username and Password schemas, for USER type extension
AttrTO serviceAuth = serviceTO.getPlainAttrMap().get("Service Authentication Type");
if (serviceAuth != null && !serviceAuth.getValues().isEmpty()
&& serviceAuth.getValues().get(0).equals("PER_USER")) {
PlainSchemaTO username = new PlainSchemaTO();
username.setKey(service.getName() + " Username");
username.setType(AttrSchemaType.String);
username.setMandatoryCondition("true");
schemaLogic.create(SchemaType.PLAIN, username);
classForUserTE.getPlainSchemas().add(username.getKey());
PlainSchemaTO password = new PlainSchemaTO();
password.setKey(service.getName() + " Password");
password.setType(AttrSchemaType.Encrypted);
password.setCipherAlgorithm(CipherAlgorithm.AES);
password.setSecretKey(SECRET_KEY);
password.setMandatoryCondition("true");
schemaLogic.create(SchemaType.PLAIN, password);
classForUserTE.getPlainSchemas().add(password.getKey());
}
} catch (NotFoundException e) {
LOG.error("Could not find service {} in the inventory, ignoring", service.getName(), e);
}
});
}
processChorSpec(chorSpec, choreography.getKey(), classForUserTE);
if (!classForUserTE.getPlainSchemas().isEmpty()) {
anyTypeClassLogic.create(classForUserTE);
......@@ -345,7 +382,7 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
userTE.getAuxClasses().add(classForUserTE.getKey());
GroupPatch groupPatch = new GroupPatch();
groupPatch.setKey(key);
groupPatch.setKey(choreography.getKey());
groupPatch.getTypeExtensions().addAll(choreography.getTypeExtensions());
groupPatch.getTypeExtensions().add(userTE);
groupLogic.update(groupPatch, false);
......@@ -353,9 +390,87 @@ public class ChoreographyLogic extends AbstractLogic<AbstractBaseBean> {
break;
case UPDATE:
// 0. check that the choreography with the passed id exists
choreography = checkChoreographyExists(id);
String oldName = choreography.getName();
// 1. update name and chroSpec
GroupPatch groupPatch = new GroupPatch();
groupPatch.setKey(choreography.getKey());
if (!oldName.equals(name)) {
groupPatch.setName(new StringReplacePatchItem.Builder().value(name).build());
}
groupPatch.getPlainAttrs().add(new AttrPatch.Builder().attrTO(
new AttrTO.Builder().schema("chorSpec").value(Base64Utility.encode(chorSpecXML)).build()).
build());
choreography = groupLogic.update(groupPatch, false).getEntity();
// 2. remove all SERVICE members
MembershipCond membershipCond = new MembershipCond();
membershipCond.setGroup(choreography.getKey());
AnyTypeCond anyTypeCond = new AnyTypeCond();
anyTypeCond.setAnyTypeKey(SERVICE_TYPE);
SearchCond serviceCond = SearchCond.getAndCond(
SearchCond.getLeafCond(anyTypeCond), SearchCond.getLeafCond(membershipCond));
int count = anyObjectLogic.searchCount(serviceCond, SyncopeConstants.ROOT_REALM);
for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
for (AnyObjectTO service : anyObjectLogic.search(serviceCond,
page, PAGE_SIZE, Collections.emptyList(), SyncopeConstants.ROOT_REALM, false)) {
AnyObjectPatch patch = new AnyObjectPatch();
patch.setKey(service.getKey());
patch.getMemberships().add(new MembershipPatch.Builder().
operation(PatchOperation.DELETE).group(choreography.getKey()).build());
anyObjectLogic.update(patch, false);
}
}
// 3. process the chroSpec
classForUserTE = anyTypeClassLogic.read(oldName);
processChorSpec(chorSpec, choreography.getKey(), classForUserTE);
if (oldName.equals(name)) {
anyTypeClassLogic.update(classForUserTE);
} else {
classForUserTE.setKey(getUSERTypeExtensionKey(name));
anyTypeClassLogic.delete(getUSERTypeExtensionKey(oldName));
anyTypeClassLogic.create(classForUserTE);
TypeExtensionTO userTE = choreography.getTypeExtension(AnyTypeKind.USER.name());
userTE.getAuxClasses().clear();
userTE.getAuxClasses().add(classForUserTE.getKey());
groupPatch = new GroupPatch();
groupPatch.setKey(choreography.getKey());
groupPatch.getTypeExtensions().addAll(choreography.getTypeExtensions());
groupLogic.update(groupPatch, false);
}
break;
case DELETE:
// 0. check that the choreography with the passed id exists
choreography = checkChoreographyExists(id);
// 1. remove the schemas and the USER type extension
try {
classForUserTE = anyTypeClassLogic.read(getUSERTypeExtensionKey(choreography.getName()));
classForUserTE.getPlainSchemas().forEach((plainSchema) -> {
schemaLogic.delete(SchemaType.PLAIN, plainSchema);
});
anyTypeClassLogic.delete(classForUserTE.getKey());
} catch (NotFoundException e) {
LOG.debug("Unexpected: could not find AnyTypeClass {}",
getUSERTypeExtensionKey(choreography.getName()), e);
}
// 2. remove the choreography group and all related choreography instances and events
groupLogic.delete(choreography.getKey(), false);
choreographyInstanceDAO.deleteByChoreographyId(id);
break;
default:
......
......@@ -436,7 +436,7 @@ public class MonitorLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
}
choreographyInstanceTO.setChoreographyName(choreography.getName());
}
choreographyInstanceTO.setStatus(getChoreographyInstanceStatus(choreographyInstancePK));
choreographyInstanceTO.setExecutionTime(getChoreographyInstanceExecutionTime(choreographyInstancePK));
......@@ -703,25 +703,10 @@ public class MonitorLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
return aVGOperationDataTOs;
}
public void deleteChoreography(final String choreographyId) {
List<ChoreographyInstance> choreographyInstances = choreographyInstanceDAO.findByChoreographyId(choreographyId);
for (ChoreographyInstance choreographyInstance : choreographyInstances) {
deleteInstance(choreographyInstance);
}
}
public void deleteInstance(final ChoreographyInstance choreographyInstance) {
if (choreographyInstance != null) {
List<Event> events = eventDAO.findByChoreographyInstancePK(choreographyInstance.getKey());
for (Event event : events) {
eventDAO.delete(event);
}
choreographyInstanceDAO.delete(choreographyInstance);
}
}
public void deleteInstance(final String choreographyInstancePK) {
deleteInstance(choreographyInstanceDAO.findById(choreographyInstancePK));
ChoreographyInstance instance = choreographyInstanceDAO.findById(choreographyInstancePK);
if (instance != null) {
choreographyInstanceDAO.delete(instance);
}
}
}
......@@ -27,9 +27,10 @@ public interface ChoreographyInstanceDAO extends DAO<ChoreographyInstance> {
ChoreographyInstance findById(String id);
ChoreographyInstance findByChoreographyInstanceId(String choreographyId, String choreographyInstanceId);
void save(ChoreographyInstance choreographyInstance);
void delete(ChoreographyInstance choreographyInstance);
void deleteByChoreographyId(String choreographyId);
}
......@@ -29,7 +29,7 @@ public interface ChoreographyInstance extends Entity {
void add(Event event);
Set<Event> getEvents();
Set<? extends Event> getEvents();
String getChoreographyId();
......
......@@ -98,4 +98,12 @@ public class JPAChoreographyInstanceDAO extends AbstractDAO<ChoreographyInstance
entityManager().remove(choreographyInstance);
}
@Transactional
@Override
public void deleteByChoreographyId(final String choreographyId) {
findByChoreographyId(choreographyId).forEach((choreographyInstance) -> {
delete(choreographyInstance);
});
}
}
......@@ -17,8 +17,11 @@ package org.apache.syncope.core.persistence.jpa.entity;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.syncope.core.persistence.api.entity.Event;
import org.apache.syncope.core.persistence.api.entity.ChoreographyInstance;
......@@ -40,7 +43,9 @@ public class JPAChoreographyInstance extends AbstractGeneratedKeyEntity implemen
@Column(nullable = false)
private String choreographyId;
private Set<Event> events = new LinkedHashSet<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true,
fetch = FetchType.LAZY, mappedBy = "choreographyInstance")
private Set<JPAEvent> events = new LinkedHashSet<>();
@Override
public String getChoreographyInstanceId() {
......@@ -74,12 +79,13 @@ public class JPAChoreographyInstance extends AbstractGeneratedKeyEntity implemen
@Override
public void add(final Event event) {
checkType(event, JPAEvent.class);
event.setChoreographyInstance(this);
events.add(event);
events.add((JPAEvent) event);
}
@Override
public Set<Event> getEvents() {
public Set<? extends Event> getEvents() {
return this.events;
}
......
......@@ -42,7 +42,7 @@ public interface ChoreographyService extends JAXRSService {
* @param name choreography name
* @param enactmentEngineKey target enactment engine instance
* @param chorSpec ChorSpec XML representation
* @return the choreography deployment id as generateed by the enactment engine
* @return the choreography deployment id as generated by the enactment engine
*/
@POST
@Consumes({ MediaType.APPLICATION_XML })
......
......@@ -37,8 +37,8 @@ public interface ConsoleInterfaceService extends JAXRSService {
/**
* This is a temporary method.
*
* @return all defined choreographies
*
* @return all defined choreographies
*/
@Path("choreographies")
@GET
......@@ -146,10 +146,6 @@ public interface ConsoleInterfaceService extends JAXRSService {