From: Laramie Crocker Date: Wed, 6 Apr 2011 16:13:08 +0000 (+0000) Subject: CSPACE-3739 added Relation service list elements X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=4cf3d48d8819ff6eaec74a4e8e7d25281ef24652;p=tmp%2Fjakarta-migration.git CSPACE-3739 added Relation service list elements --- diff --git a/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java b/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java index 0df8fba6d..f2eb1fa5f 100755 --- a/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java +++ b/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java @@ -160,6 +160,16 @@ public class Tools { return ""; } String s = e.toString() + "\r\n -- message: " + e.getMessage(); + + StringBuffer causeBuffer = new StringBuffer(); + Throwable cause = e.getCause(); + while (cause != null){ + causeBuffer.append(cause.getClass().getName()+"::"+cause.getMessage()+"\r\n"); + cause = cause.getCause(); + } + if (causeBuffer.length()>0) s = s + "\r\n -- Causes: "+causeBuffer.toString(); + + s = s + "\r\n -- Stack Trace: \r\n -- " + getStackTrace(e); return s; } diff --git a/services/common/src/main/cspace/config/services/tenant-bindings-proto.xml b/services/common/src/main/cspace/config/services/tenant-bindings-proto.xml index d70616053..a17acfc22 100644 --- a/services/common/src/main/cspace/config/services/tenant-bindings-proto.xml +++ b/services/common/src/main/cspace/config/services/tenant-bindings-proto.xml @@ -1280,6 +1280,12 @@ org.collectionspace.services.relation.nuxeo.RelationValidatorHandler + + + objectNamePropertysubjectID + objectNumberPropertypredicateDisplayName + + org.collectionspace.services.objectexit.nuxeo.ObjectExitValidatorHandler + + objectNamePropertycurrentOwner + objectNumberPropertyexitNumber + + + + + default-domain + + + org.collectionspace.services.imports.nuxeo.ImportsDocumentModelHandler + + + org.collectionspace.services.imports.nuxeo.ImportsDocumentModelHandler + + imports + imports + importsField|uri|csid + org.collectionspace.services.imports.ImportsCommonList + org.collectionspace.services.imports.ImportsCommonList$ImportsListItem + + getImportsListItem + + + importsField + importsField + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1416,6 +1482,11 @@ org.collectionspace.services.relation.nuxeo.RelationValidatorHandler + + objectNamePropertydocumentId1 + objectNumberPropertypredicateDisplayName + + + + + + default-domain + + + org.collectionspace.services.imports.nuxeo.ImportsDocumentModelHandler + + + org.collectionspace.services.imports.nuxeo.ImportsDocumentModelHandler + + imports + imports + importsField|uri|csid + org.collectionspace.services.imports.ImportsCommonList + org.collectionspace.services.imports.ImportsCommonList$ImportsListItem + + getImportsListItem + + + importsField + importsField + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1281,6 +1343,12 @@ org.collectionspace.services.relation.nuxeo.RelationValidatorHandler + + + objectNamePropertysubjectID + objectNumberPropertypredicateDisplayName + + serviceBindings = new Hashtable(); + //tenant-qualified service object name to service name, service binding + private Hashtable docTypes = + new Hashtable(); + + public TenantBindingConfigReaderImpl(String serverRootDir) { super(serverRootDir); } @@ -161,6 +167,15 @@ public class TenantBindingConfigReaderImpl String key = getTenantQualifiedServiceName(tenantBinding.getId(), serviceBinding.getName()); serviceBindings.put(key, serviceBinding); + + if (serviceBinding!=null){ + ServiceObjectType objectType = serviceBinding.getObject(); + if (objectType!=null){ + String docType = objectType.getName(); + String docTypeKey = getTenantQualifiedIdentifier(tenantBinding.getId(), docType); + docTypes.put(docTypeKey, serviceBinding); + } + } if (logger.isDebugEnabled()) { logger.debug("readServiceBindings() added service " + " name=" + key @@ -239,6 +254,17 @@ public class TenantBindingConfigReaderImpl return serviceBindings.get(key); } + /** + * getServiceBinding gets service binding for given tenant for a given service + * @param tenantId + * @param docType + * @return + */ + public ServiceBindingType getServiceBindingForDocType (String tenantId, String docType) { + String key = getTenantQualifiedIdentifier(tenantId, docType); + return docTypes.get(key); + } + /** * getServiceBinding gets service binding for given tenant for a given service * @param tenantId @@ -272,6 +298,9 @@ public class TenantBindingConfigReaderImpl return tenantId + "." + serviceName.toLowerCase(); } + public static String getTenantQualifiedIdentifier(String tenantId, String identifier) { + return tenantId + "." + identifier; + } /** * Sets properties in the passed list on the local properties for this TenantBinding. * Note: will only set properties not already set on the TenantBinding. @@ -297,4 +326,8 @@ public class TenantBindingConfigReaderImpl } } } + + public String getResourcesDir(){ + return getConfigRootDir() + File.separator + "resources"; + } } diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceBindingUtils.java b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceBindingUtils.java index 8edfecbd0..f3587a53f 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceBindingUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceBindingUtils.java @@ -145,7 +145,7 @@ public class ServiceBindingUtils { return (String)docModel.getPropertyValue(propName); } catch(ClientException ce) { throw new RuntimeException( - "getMappedFieldInDoc: Problem fetching: "+propName, ce); + "getMappedFieldInDoc: Problem fetching: "+propName+" logicalfieldName: "+logicalFieldName+" docModel: "+docModel, ce); } } diff --git a/services/imports/build.xml b/services/imports/build.xml index 6307d113e..59ec3f0ee 100755 --- a/services/imports/build.xml +++ b/services/imports/build.xml @@ -109,6 +109,9 @@ + + + + + + + @@ -59,7 +63,19 @@ - + + + + + + + + + + + + + @@ -69,13 +85,16 @@ - + + + + diff --git a/services/pom.xml b/services/pom.xml index 35fd25fbe..c25091c14 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -20,6 +20,7 @@ be built after the security module and common modules are built --> + common-api authentication authorization blob @@ -28,7 +29,6 @@ hyperjaxb common authorization-mgt - common-api common-test account note diff --git a/services/relation/3rdparty/nuxeo-platform-cs-relation/src/main/resources/schemas/relations_common.xsd b/services/relation/3rdparty/nuxeo-platform-cs-relation/src/main/resources/schemas/relations_common.xsd index 3c9a420b0..abfb09147 100644 --- a/services/relation/3rdparty/nuxeo-platform-cs-relation/src/main/resources/schemas/relations_common.xsd +++ b/services/relation/3rdparty/nuxeo-platform-cs-relation/src/main/resources/schemas/relations_common.xsd @@ -35,12 +35,15 @@ + + + diff --git a/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java b/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java index 95799715c..bfce67d25 100644 --- a/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java +++ b/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java @@ -175,7 +175,7 @@ public class RelationServiceTest extends AbstractServiceTestImpl { } Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + Assert.assertEquals(statusCode, STATUS_INTERNAL_SERVER_ERROR); //should be an error: same objectID and subjectID are not allowed by validator. } // Failure outcomes @@ -825,7 +825,7 @@ public class RelationServiceTest extends AbstractServiceTestImpl { fillRelation(relationCommon, identifier); return relationCommon; } - + private PoxPayloadOut createRelationInstance(RelationsCommon relation) { PoxPayloadOut result = new PoxPayloadOut(this.getServicePathComponent()); PayloadOutputPart commonPart = @@ -853,7 +853,7 @@ public class RelationServiceTest extends AbstractServiceTestImpl { /** * Fills the relation. * - * @param relation the relation + * @param relationCommon the relation * @param identifier the identifier */ private void fillRelation(RelationsCommon relationCommon, String identifier) { @@ -868,7 +868,7 @@ public class RelationServiceTest extends AbstractServiceTestImpl { /** * Fills the relation. * - * @param relation the relation + * @param relationCommon the relation * @param documentId1 the document id1 * @param documentType1 the document type1 * @param documentId2 the document id2 diff --git a/services/relation/service/src/main/java/org/collectionspace/services/relation/RelationResource.java b/services/relation/service/src/main/java/org/collectionspace/services/relation/RelationResource.java index e92eebce9..de0176726 100644 --- a/services/relation/service/src/main/java/org/collectionspace/services/relation/RelationResource.java +++ b/services/relation/service/src/main/java/org/collectionspace/services/relation/RelationResource.java @@ -42,10 +42,10 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; -import org.collectionspace.services.client.IQueryManager; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.client.PoxPayloadOut; -//import org.collectionspace.services.common.query.IQueryManager; +import org.collectionspace.services.common.context.ServiceBindingUtils; +import org.collectionspace.services.common.query.IQueryManager; import org.collectionspace.services.common.relation.IRelationsManager; import org.collectionspace.services.common.relation.nuxeo.RelationsUtils; import org.collectionspace.services.common.AbstractMultiPartCollectionSpaceResourceImpl; @@ -55,11 +55,14 @@ import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.document.DocumentHandler; import org.collectionspace.services.common.security.UnauthorizedException; +import org.collectionspace.services.common.service.ServiceBindingType; import org.jboss.resteasy.util.HttpResponseCodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + /** * The Class RelationResource. */ @@ -104,8 +107,7 @@ public class RelationResource extends /** * Creates the relation. * - * @param input the input - * + * * @return the response */ @POST @@ -231,8 +233,7 @@ public class RelationResource extends * Update relation. * * @param csid the csid - * @param theUpdate the the update - * + * * @return the multipart output */ @PUT @@ -353,6 +354,17 @@ public class RelationResource extends handler.getDocumentFilter().appendWhereClause(relationClause, IQueryManager.SEARCH_QUALIFIER_AND); getRepositoryClient(ctx).getFiltered(ctx, handler); relationList = (RelationsCommonList)handler.getCommonPartList(); + + + + /*handler. + int i=0; + for (RelationsCommonList.RelationListItem item: relationList.getRelationListItem()){ + i++; + item.setSubjectDocumentNumber("FooSubjectDocumentNumber" + i); + item.setDoc + } + */ } catch (UnauthorizedException ue) { Response response = Response.status(Response.Status.UNAUTHORIZED).entity( "Get relations failed reason " + @@ -369,5 +381,25 @@ public class RelationResource extends } return relationList; - } + } + + /* + private void fillListItem(){ + String docType = docModel.getDocumentType().getName(); + ServiceBindingType sb = queriedServiceBindings.get(docType); + if (sb == null) { throw new RuntimeException( "getAuthorityRefDocs: No Service Binding for docType: " + docType); } + String serviceContextPath = "/" + sb.getName().toLowerCase() + "/"; + // The id and URI are the same on all doctypes + ilistItem.setDocId(csid); + ilistItem.setUri(serviceContextPath + csid); + ilistItem.setDocType(docType); + ilistItem.setDocNumber( + ServiceBindingUtils.getMappedFieldInDoc(sb, ServiceBindingUtils.OBJ_NUMBER_PROP, docModel)); + ilistItem.setDocName( + ServiceBindingUtils.getMappedFieldInDoc(sb, + ServiceBindingUtils.OBJ_NAME_PROP, docModel)); + + } + */ } + diff --git a/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationDocumentModelHandler.java b/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationDocumentModelHandler.java index 17ab12de0..3996c8ec3 100644 --- a/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationDocumentModelHandler.java +++ b/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationDocumentModelHandler.java @@ -24,13 +24,19 @@ package org.collectionspace.services.relation.nuxeo; import java.util.Iterator; -import java.util.List; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.common.ServiceMain; +import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; +import org.collectionspace.services.common.context.ServiceBindingUtils; import org.collectionspace.services.common.relation.RelationJAXBSchema; import org.collectionspace.services.common.relation.nuxeo.RelationConstants; import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.repository.RepositoryClient; +import org.collectionspace.services.common.repository.RepositoryClientFactory; +import org.collectionspace.services.common.service.ServiceBindingType; +import org.collectionspace.services.nuxeo.util.NuxeoUtils; import org.collectionspace.services.relation.RelationsCommon; import org.collectionspace.services.relation.RelationsCommonList; import org.collectionspace.services.relation.RelationsCommonList.RelationListItem; @@ -38,11 +44,10 @@ import org.collectionspace.services.relation.RelationsCommonList.RelationListIte import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.jaxb.AbstractCommonList; import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl; -import org.collectionspace.services.nuxeo.util.NuxeoUtils; -import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; -import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput; +import org.collectionspace.services.relation.RelationsDocListItem; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; +import org.nuxeo.ecm.core.api.repository.RepositoryInstance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +60,6 @@ import org.slf4j.LoggerFactory; public class RelationDocumentModelHandler extends RemoteDocumentModelHandlerImpl { - /** The logger. */ private final Logger logger = LoggerFactory.getLogger(RelationDocumentModelHandler.class); /** * relation is used to stash JAXB object to use when handle is called @@ -68,128 +72,139 @@ public class RelationDocumentModelHandler */ private RelationsCommonList relationList; - - /** - * getCommonObject get associated Relation - * @return relation - */ @Override public RelationsCommon getCommonPart() { return relation; } - /** - * setCommonObject set associated relation - * @param relation - */ @Override public void setCommonPart(RelationsCommon theRelation) { this.relation = theRelation; } - /** - * getRelationList get associated Relation (for index/GET_ALL) - * @return relationCommonList + /**get associated Relation (for index/GET_ALL) */ @Override public RelationsCommonList getCommonPartList() { return relationList; } - /* (non-Javadoc) - * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#setCommonPartList(java.lang.Object) - */ @Override public void setCommonPartList(RelationsCommonList theRelationList) { this.relationList = theRelationList; } - /* (non-Javadoc) - * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractCommonPart(org.collectionspace.services.common.document.DocumentWrapper) - */ @Override public RelationsCommon extractCommonPart(DocumentWrapper wrapDoc) throws Exception { throw new UnsupportedOperationException(); } - /* (non-Javadoc) - * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillCommonPart(java.lang.Object, org.collectionspace.services.common.document.DocumentWrapper) - */ @Override public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper wrapDoc) throws Exception { throw new UnsupportedOperationException(); } - /* (non-Javadoc) - * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractCommonPartList(org.collectionspace.services.common.document.DocumentWrapper) - */ @Override public RelationsCommonList extractCommonPartList(DocumentWrapper wrapDoc) throws Exception { RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc) ; - AbstractCommonList commonList = (AbstractCommonList) relList; - commonList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|objectCsid|uri|csid"); - List itemList = relList.getRelationListItem(); + relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|objectCsid|uri|csid|subject|object"); + ServiceContext ctx = getServiceContext(); + String serviceContextPath = getServiceContextPath(); + + TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader(); + String serviceName = getServiceContext().getServiceName().toLowerCase(); + ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName); + Iterator iter = wrapDoc.getWrappedObject().iterator(); while(iter.hasNext()){ DocumentModel docModel = iter.next(); - RelationListItem relListItem = getRelationListItem(getServiceContext(), - docModel, getServiceContextPath()); - itemList.add(relListItem); + RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath); + relList.getRelationListItem().add(relListItem); } return relList; } - - - /* (non-Javadoc) - * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper) - @Override - public void fillAllParts(DocumentWrapper wrapDoc) throws Exception { - super.fillAllParts(wrapDoc); - } - */ - - /** - * Gets the relation list item. - * + /** Gets the relation list item, looking up the subject and object documents, and getting summary + * info via the objectName and objectNumber properties in tenant-bindings. * @param ctx the ctx + * @param sbt the ServiceBindingType of Relations service + * @param tReader the tenant-bindings reader, for looking up docnumber and docname * @param docModel the doc model * @param serviceContextPath the service context path - * @return the relation list item + * @return the relation list item, with nested subject and object summary info. * @throws Exception the exception */ private RelationListItem getRelationListItem(ServiceContext ctx, - DocumentModel docModel, - String serviceContextPath) throws Exception { + ServiceBindingType sbt, + TenantBindingConfigReaderImpl tReader, + DocumentModel docModel, + String serviceContextPath) throws Exception { RelationListItem relationListItem = new RelationListItem(); - String id = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString()); + String id = getCsid(docModel); relationListItem.setCsid(id); - // - // Subject - // - relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(), - RelationJAXBSchema.DOCUMENT_ID_1)); - // - // Predicate - // - relationListItem.setRelationshipType((String) docModel.getProperty(ctx.getCommonPartLabel(), - RelationJAXBSchema.RELATIONSHIP_TYPE)); - relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(), - RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME)); - // - // Object - // - relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(), - RelationJAXBSchema.DOCUMENT_ID_2)); + + relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(), RelationJAXBSchema.DOCUMENT_ID_1)); + + String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(), RelationJAXBSchema.RELATIONSHIP_TYPE); + relationListItem.setRelationshipType(predicate); + relationListItem.setPredicate(predicate); //predicate is new name for relationshipType. + relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(), RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME)); + + relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(), RelationJAXBSchema.DOCUMENT_ID_2)); relationListItem.setUri(serviceContextPath + id); + + //Now fill in summary info for the related docs: subject and object. + String subjectCsid = relationListItem.getSubjectCsid(); + RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader); + relationListItem.setSubject(subject); + + String objectCsid = relationListItem.getObjectCsid(); + RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader); + relationListItem.setObject(object); + return relationListItem; } - /* (non-Javadoc) - * @see org.collectionspace.services.common.document.AbstractMultipartDocumentHandlerImpl#getQProperty(java.lang.String) - */ + protected RelationsDocListItem createRelationsDocListItem(ServiceContext ctx, + ServiceBindingType sbt, + String itemCsid, + TenantBindingConfigReaderImpl tReader) throws Exception { + RelationsDocListItem item = new RelationsDocListItem(); + // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid); + DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, itemCsid); //null if not found. + if (itemDocModel!=null){ + String itemDocType = itemDocModel.getDocumentType().getName(); + + //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype + //ServiceBindingType itemSbt = tReader.getServiceBinding(ctx.getTenantId(), itemDocType); + ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType); + //String bar = "\r\n=======================\r\n"; + //System.out.println(bar+"itemDocType: "+itemDocType); + //System.out.println(bar+"ServiceBindingType: "+itemSbt); + //System.out.println(bar); + + try { + String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel); + item.setName(itemDocname); + //System.out.println("\r\n\r\n\r\n=================\r\n~~found prop : "+ServiceBindingUtils.OBJ_NAME_PROP+" in :"+itemDocname); + } catch (Throwable t){ + System.out.println("\r\n\r\n\r\n=================\r\n NOTE: "+itemDocModel+" field "+ServiceBindingUtils.OBJ_NAME_PROP+" not found in DocModel: "+itemDocModel.getName()+" inner: "+t.getMessage()); + } + try { + String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel); + item.setNumber(itemDocnumber); + //System.out.println("\r\n\r\n\r\n=================\r\n~~found prop : "+ServiceBindingUtils.OBJ_NUMBER_PROP+" in :"+itemDocnumber); + } catch (Throwable t){ + System.out.println("\r\n\r\n\r\n=================\r\n NOTE: field "+ServiceBindingUtils.OBJ_NUMBER_PROP+" not found in DocModel: "+itemDocModel.getName()+" inner: "+t.getMessage()); + } + item.setType(itemDocType); + } + item.setCsid(itemCsid); + return item; + } + @Override public String getQProperty(String prop) { return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;