/*
* Copyright (c) 2010-2016 Evolveum
*
* 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.evolveum.midpoint.web.component.prism;
import com.evolveum.midpoint.common.refinery.CompositeRefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl;
import com.evolveum.midpoint.gui.api.util.ModelServiceLocator;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.CapabilityUtil;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType;
import org.apache.commons.lang.Validate;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import java.text.DateFormat;
import java.util.*;
/**
* @author Viliam Repan (lazyman)
*/
public class ContainerWrapperFactory {
private static final Trace LOGGER = TraceManager.getTrace(ContainerWrapperFactory.class);
private static final String DOT_CLASS = ContainerWrapperFactory.class.getName() + ".";
private static final String CREATE_PROPERTIES = DOT_CLASS + "createProperties";
private static final List<QName> INHERITED_OBJECT_ATTRIBUTES = Arrays.asList(
ObjectType.F_NAME,
ObjectType.F_DESCRIPTION,
ObjectType.F_FETCH_RESULT,
ObjectType.F_PARENT_ORG,
ObjectType.F_PARENT_ORG_REF,
ObjectType.F_TENANT_REF,
FocusType.F_LINK,
FocusType.F_LINK_REF);
private ModelServiceLocator modelServiceLocator;
private OperationResult result;
public ContainerWrapperFactory(ModelServiceLocator modelServiceLocator) {
Validate.notNull(modelServiceLocator, "Service locator must not be null");
this.modelServiceLocator = modelServiceLocator;
}
public OperationResult getResult() {
return result;
}
public <T extends PrismContainer> ContainerWrapper createContainerWrapper(ObjectWrapper objectWrapper,
T container,
ContainerStatus status,
ItemPath path) {
result = new OperationResult(CREATE_PROPERTIES);
ContainerWrapper cWrapper = new ContainerWrapper(objectWrapper, container, status, path);
List<ItemWrapper> properties = createProperties(cWrapper, result);
cWrapper.setProperties(properties);
cWrapper.computeStripes();
return cWrapper;
}
public <T extends PrismContainer> ContainerWrapper createContainerWrapper(T container, ContainerStatus status, ItemPath path, boolean readonly) {
result = new OperationResult(CREATE_PROPERTIES);
ContainerWrapper cWrapper = new ContainerWrapper(container, status, path, readonly);
List<ItemWrapper> properties = createProperties(cWrapper, result);
cWrapper.setProperties(properties);
cWrapper.computeStripes();
return cWrapper;
}
private List<ItemWrapper> createProperties(ContainerWrapper cWrapper, OperationResult result) {
ObjectWrapper objectWrapper = cWrapper.getObject();
PrismContainer container = cWrapper.getItem();
PrismContainerDefinition containerDefinition = cWrapper.getItemDefinition();
List<ItemWrapper> properties = new ArrayList<>();
PrismContainerDefinition definition;
if (objectWrapper == null) {
definition = containerDefinition;
} else {
PrismObject parent = objectWrapper.getObject();
Class clazz = parent.getCompileTimeClass();
if (ShadowType.class.isAssignableFrom(clazz)) {
QName name = containerDefinition.getName();
if (ShadowType.F_ATTRIBUTES.equals(name)) {
try {
definition = objectWrapper.getRefinedAttributeDefinition();
if (definition == null) {
PrismReference resourceRef = parent.findReference(ShadowType.F_RESOURCE_REF);
PrismObject<ResourceType> resource = resourceRef.getValue().getObject();
definition = modelServiceLocator
.getModelInteractionService()
.getEditObjectClassDefinition((PrismObject<ShadowType>) objectWrapper.getObject(), resource,
AuthorizationPhaseType.REQUEST)
.toResourceAttributeContainerDefinition();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Refined account def:\n{}", definition.debugDump());
}
}
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER,
"Couldn't load definitions from refined schema for shadow", ex);
result.recordFatalError(
"Couldn't load definitions from refined schema for shadow, reason: "
+ ex.getMessage(), ex);
return properties;
}
} else {
definition = containerDefinition;
}
} else if (ResourceType.class.isAssignableFrom(clazz)) {
if (containerDefinition != null) {
definition = containerDefinition;
} else {
definition = container.getDefinition();
}
} else {
definition = containerDefinition;
}
}
if (definition == null) {
LOGGER.error("Couldn't get property list from null definition {}",
new Object[]{container.getElementName()});
return properties;
}
// assignments are treated in a special way -- we display names of
// org.units and roles
// (but only if ObjectWrapper.isShowAssignments() is true; otherwise
// they are filtered out by ObjectWrapper)
if (container.getCompileTimeClass() != null
&& AssignmentType.class.isAssignableFrom(container.getCompileTimeClass())) {
for (Object o : container.getValues()) {
PrismContainerValue<AssignmentType> pcv = (PrismContainerValue<AssignmentType>) o;
AssignmentType assignmentType = pcv.asContainerable();
if (assignmentType.getTargetRef() == null) {
continue;
}
// hack... we want to create a definition for Name
// PrismPropertyDefinition def = ((PrismContainerValue)
// pcv.getContainer().getParent()).getContainer().findProperty(ObjectType.F_NAME).getDefinition();
PrismPropertyDefinitionImpl def = new PrismPropertyDefinitionImpl(ObjectType.F_NAME,
DOMUtil.XSD_STRING, pcv.getPrismContext());
if (OrgType.COMPLEX_TYPE.equals(assignmentType.getTargetRef().getType())) {
def.setDisplayName("Org.Unit");
def.setDisplayOrder(100);
} else if (RoleType.COMPLEX_TYPE.equals(assignmentType.getTargetRef().getType())) {
def.setDisplayName("Role");
def.setDisplayOrder(200);
} else {
continue;
}
PrismProperty<Object> temp = def.instantiate();
String value = formatAssignmentBrief(assignmentType);
temp.setValue(new PrismPropertyValue<Object>(value));
// TODO: do this.isReadOnly() - is that OK? (originally it was the default behavior for all cases)
properties.add(new PropertyWrapper(cWrapper, temp, cWrapper.isReadonly(), ValueStatus.NOT_CHANGED));
}
} else if (isShadowAssociation(cWrapper)) {
// HACK: this should not be here. Find a better place.
cWrapper.setDisplayName("prismContainer.shadow.associations");
PrismContext prismContext = objectWrapper.getObject().getPrismContext();
Map<QName, PrismContainer<ShadowAssociationType>> assocMap = new HashMap<>();
PrismContainer<ShadowAssociationType> associationContainer = cWrapper.getItem();
if (associationContainer != null && associationContainer.getValues() != null) {
// Do NOT load shadows here. This will be huge overhead if there are many associations.
// Load them on-demand (if necessary at all).
List<PrismContainerValue<ShadowAssociationType>> associations = associationContainer.getValues();
if (associations != null) {
for (PrismContainerValue<ShadowAssociationType> cval : associations) {
ShadowAssociationType associationType = cval.asContainerable();
QName assocName = associationType.getName();
PrismContainer<ShadowAssociationType> fractionalContainer = assocMap.get(assocName);
if (fractionalContainer == null) {
fractionalContainer = new PrismContainer<>(ShadowType.F_ASSOCIATION, ShadowAssociationType.class, cval.getPrismContext());
fractionalContainer.setDefinition(cval.getParent().getDefinition());
// HACK: set the name of the association as the element name so wrapper.getName() will return correct data.
fractionalContainer.setElementName(assocName);
assocMap.put(assocName, fractionalContainer);
}
try {
fractionalContainer.add(cval.clone());
} catch (SchemaException e) {
// Should not happen
throw new SystemException("Unexpected error: " + e.getMessage(), e);
}
}
}
}
PrismReference resourceRef = objectWrapper.getObject().findReference(ShadowType.F_RESOURCE_REF);
PrismObject<ResourceType> resource = resourceRef.getValue().getObject();
// HACK. The revive should not be here. Revive is no good. The next use of the resource will
// cause parsing of resource schema. We need some centralized place to maintain live cached copies
// of resources.
try {
resource.revive(prismContext);
} catch (SchemaException e) {
throw new SystemException(e.getMessage(), e);
}
RefinedResourceSchema refinedSchema;
CompositeRefinedObjectClassDefinition rOcDef;
try {
refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource);
rOcDef = refinedSchema.determineCompositeObjectClassDefinition(objectWrapper.getObject());
} catch (SchemaException e) {
throw new SystemException(e.getMessage(), e);
}
// Make sure even empty associations have their wrappers so they can be displayed and edited
for (RefinedAssociationDefinition assocDef : rOcDef.getAssociationDefinitions()) {
QName name = assocDef.getName();
if (!assocMap.containsKey(name)) {
PrismContainer<ShadowAssociationType> fractionalContainer = new PrismContainer<>(ShadowType.F_ASSOCIATION, ShadowAssociationType.class, prismContext);
fractionalContainer.setDefinition(cWrapper.getItemDefinition());
// HACK: set the name of the association as the element name so wrapper.getName() will return correct data.
fractionalContainer.setElementName(name);
assocMap.put(name, fractionalContainer);
}
}
for (Map.Entry<QName, PrismContainer<ShadowAssociationType>> assocEntry : assocMap.entrySet()) {
RefinedAssociationDefinition assocRDef = rOcDef.findAssociationDefinition(assocEntry.getKey());
AssociationWrapper assocWrapper = new AssociationWrapper(cWrapper, assocEntry.getValue(),
cWrapper.isReadonly(), ValueStatus.NOT_CHANGED, assocRDef);
properties.add(assocWrapper);
}
} else { // if not an assignment
if ((container.getValues().size() == 1 || container.getValues().isEmpty())
&& (containerDefinition == null || containerDefinition.isSingleValue())) {
// there's no point in showing properties for non-single-valued
// parent containers,
// so we continue only if the parent is single-valued
Collection<ItemDefinition> propertyDefinitions = definition.getDefinitions();
for (ItemDefinition itemDef : propertyDefinitions) {
//TODO temporary decision to hide adminGuiConfiguration attribute (MID-3305)
if (itemDef != null && itemDef.getName() != null && itemDef.getName().getLocalPart() != null &&
itemDef.getName().getLocalPart().equals("adminGuiConfiguration")){
continue;
}
if (itemDef instanceof PrismPropertyDefinition) {
PrismPropertyDefinition def = (PrismPropertyDefinition) itemDef;
if (def.isIgnored() || skipProperty(def)) {
continue;
}
if (!cWrapper.isShowInheritedObjectAttributes()
&& INHERITED_OBJECT_ATTRIBUTES.contains(def.getName())) {
continue;
}
// capability handling for activation properties
if (isShadowActivation(cWrapper) && !hasActivationCapability(cWrapper, def)) {
continue;
}
if (isShadowAssociation(cWrapper)) {
continue;
}
PrismProperty property = container.findProperty(def.getName());
boolean propertyIsReadOnly;
// decision is based on parent object status, not this
// container's one (because container can be added also
// to an existing object)
if (objectWrapper == null || objectWrapper.getStatus() == ContainerStatus.MODIFYING) {
propertyIsReadOnly = cWrapper.isReadonly() || !def.canModify();
} else {
propertyIsReadOnly = cWrapper.isReadonly() || !def.canAdd();
}
if (property == null) {
properties.add(new PropertyWrapper(cWrapper, def.instantiate(), propertyIsReadOnly,
ValueStatus.ADDED));
} else {
properties.add(new PropertyWrapper(cWrapper, property, propertyIsReadOnly,
ValueStatus.NOT_CHANGED));
}
} else if (itemDef instanceof PrismReferenceDefinition) {
PrismReferenceDefinition def = (PrismReferenceDefinition) itemDef;
if (INHERITED_OBJECT_ATTRIBUTES.contains(def.getName())) {
continue;
}
PrismReference reference = container.findReference(def.getName());
boolean propertyIsReadOnly;
// decision is based on parent object status, not this
// container's one (because container can be added also
// to an existing object)
if (objectWrapper == null || objectWrapper.getStatus() == ContainerStatus.MODIFYING) {
propertyIsReadOnly = !def.canModify();
} else {
propertyIsReadOnly = !def.canAdd();
}
if (reference == null) {
properties.add(new ReferenceWrapper(cWrapper, def.instantiate(), propertyIsReadOnly,
ValueStatus.ADDED));
} else {
properties.add(new ReferenceWrapper(cWrapper, reference, propertyIsReadOnly,
ValueStatus.NOT_CHANGED));
}
}
}
}
}
Collections.sort(properties, new ItemWrapperComparator());
result.recomputeStatus();
return properties;
}
private boolean isShadowAssociation(ContainerWrapper cWrapper) {
ObjectWrapper oWrapper = cWrapper.getObject();
if (oWrapper == null) {
return false;
}
PrismContainer container = cWrapper.getItem();
if (!ShadowType.class.isAssignableFrom(oWrapper.getObject().getCompileTimeClass())) {
return false;
}
if (!ShadowType.F_ASSOCIATION.equals(container.getElementName())) {
return false;
}
return true;
}
private boolean isShadowActivation(ContainerWrapper cWrapper) {
ObjectWrapper oWrapper = cWrapper.getObject();
if (oWrapper == null) {
return false;
}
PrismContainer container = cWrapper.getItem();
if (!ShadowType.class.isAssignableFrom(oWrapper.getObject().getCompileTimeClass())) {
return false;
}
if (!ShadowType.F_ACTIVATION.equals(container.getElementName())) {
return false;
}
return true;
}
private boolean hasActivationCapability(ContainerWrapper cWrapper, PrismPropertyDefinition def) {
ObjectWrapper oWrapper = cWrapper.getObject();
ShadowType shadow = (ShadowType) oWrapper.getObject().asObjectable();
ActivationCapabilityType cap = ResourceTypeUtil.getEffectiveCapability(shadow.getResource(),
ActivationCapabilityType.class);
if (ActivationType.F_VALID_FROM.equals(def.getName()) && CapabilityUtil.getEffectiveActivationValidFrom(cap) == null) {
return false;
}
if (ActivationType.F_VALID_TO.equals(def.getName()) && CapabilityUtil.getEffectiveActivationValidTo(cap) == null) {
return false;
}
if (ActivationType.F_ADMINISTRATIVE_STATUS.equals(def.getName()) && CapabilityUtil.getEffectiveActivationStatus(cap) == null) {
return false;
}
return true;
}
// FIXME temporary - brutal hack - the following three methods are copied from
// AddRoleAssignmentAspect - Pavol M.
private String formatAssignmentBrief(AssignmentType assignment) {
StringBuilder sb = new StringBuilder();
if (assignment.getTarget() != null) {
sb.append(assignment.getTarget().getName());
} else {
sb.append(assignment.getTargetRef().getOid());
}
if (assignment.getActivation() != null
&& (assignment.getActivation().getValidFrom() != null || assignment.getActivation()
.getValidTo() != null)) {
sb.append(" ");
sb.append("(");
sb.append(formatTimeIntervalBrief(assignment));
sb.append(")");
}
if (assignment.getActivation() != null) {
switch (assignment.getActivation().getAdministrativeStatus()) {
case ARCHIVED:
sb.append(", archived");
break; // TODO i18n
case ENABLED:
sb.append(", enabled");
break;
case DISABLED:
sb.append(", disabled");
break;
}
}
return sb.toString();
}
public static String formatTimeIntervalBrief(AssignmentType assignment) {
StringBuilder sb = new StringBuilder();
if (assignment != null
&& assignment.getActivation() != null
&& (assignment.getActivation().getValidFrom() != null || assignment.getActivation()
.getValidTo() != null)) {
if (assignment.getActivation().getValidFrom() != null
&& assignment.getActivation().getValidTo() != null) {
sb.append(formatTime(assignment.getActivation().getValidFrom()));
sb.append("-");
sb.append(formatTime(assignment.getActivation().getValidTo()));
} else if (assignment.getActivation().getValidFrom() != null) {
sb.append("from ");
sb.append(formatTime(assignment.getActivation().getValidFrom()));
} else {
sb.append("to ");
sb.append(formatTime(assignment.getActivation().getValidTo()));
}
}
return sb.toString();
}
private static String formatTime(XMLGregorianCalendar time) {
DateFormat formatter = DateFormat.getDateInstance();
return formatter.format(time.toGregorianCalendar().getTime());
}
/**
* This methods check if we want to show property in form (e.g.
* failedLogins, fetchResult, lastFailedLoginTimestamp must be invisible)
*
* @return
* @deprecated will be implemented through annotations in schema
*/
@Deprecated
private boolean skipProperty(PrismPropertyDefinition def) {
final List<QName> names = new ArrayList<>();
names.add(PasswordType.F_FAILED_LOGINS);
names.add(PasswordType.F_LAST_FAILED_LOGIN);
names.add(PasswordType.F_LAST_SUCCESSFUL_LOGIN);
names.add(PasswordType.F_PREVIOUS_SUCCESSFUL_LOGIN);
names.add(ObjectType.F_FETCH_RESULT);
// activation
names.add(ActivationType.F_EFFECTIVE_STATUS);
names.add(ActivationType.F_VALIDITY_STATUS);
// user
names.add(UserType.F_RESULT);
// org and roles
names.add(OrgType.F_APPROVAL_PROCESS);
names.add(OrgType.F_APPROVER_EXPRESSION);
names.add(OrgType.F_AUTOMATICALLY_APPROVED);
names.add(OrgType.F_CONDITION);
for (QName name : names) {
if (name.equals(def.getName())) {
return true;
}
}
return false;
}
}