001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 018 */ 019 020package org.nuxeo.ecm.core.model; 021 022import java.io.Serializable; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029 030import org.nuxeo.ecm.core.api.CoreInstance; 031import org.nuxeo.ecm.core.api.CoreSession; 032import org.nuxeo.ecm.core.api.DocumentModel; 033import org.nuxeo.ecm.core.api.DocumentNotFoundException; 034import org.nuxeo.ecm.core.api.IdRef; 035import org.nuxeo.ecm.core.api.PathRef; 036import org.nuxeo.ecm.core.api.local.LocalException; 037import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 038 039/** 040 * This {@link ObjectResolver} allows to manage integrity for fields containing {@link DocumentModel} references (id or 041 * path). 042 * <p> 043 * Resolved references must be either a path or an id, default mode is id. Storing path keep link with place in the 044 * Document hierarchy no matter which Document is referenced. Storing id track the Document no matter where the Document 045 * is stored. 046 * </p> 047 * <p> 048 * All references, id or path, are prefixed with the document expected repository name. For example : 049 * </p> 050 * <ul> 051 * <li>default:352c21bc-f908-4507-af99-411d3d84ee7d</li> 052 * <li>test:/path/to/my/doc</li> 053 * </ul> 054 * <p> 055 * The {@link #fetch(Object)} method returns {@link DocumentModel}. The {@link #fetch(Class, Object)} returns 056 * {@link DocumentModel} or specific document adapter. 057 * </p> 058 * <p> 059 * To use it, put the following code in your schema XSD : 060 * </p> 061 * 062 * <pre> 063 * {@code 064 * <!-- default resolver is an id based resolver --> 065 * <xs:simpleType name="favoriteDocument1"> 066 * <xs:restriction base="xs:string" ref:resolver="documentResolver" /> 067 * </xs:simpleType> 068 * 069 * <!-- store id --> 070 * <xs:simpleType name="favoriteDocument2"> 071 * <xs:restriction base="xs:string" ref:resolver="documentResolver" ref:store="id" /> 072 * </xs:simpleType> 073 * 074 * <!-- store path --> 075 * <xs:simpleType name="bestDocumentRepositoryPlace"> 076 * <xs:restriction base="xs:string" ref:resolver="documentResolver" ref:store="path" /> 077 * </xs:simpleType> 078 * } 079 * </pre> 080 * 081 * @since 7.1 082 */ 083public class DocumentModelResolver implements ObjectResolver { 084 085 private static final long serialVersionUID = 1L; 086 087 private static final String DEFAULT_REPO_NAME = "default"; 088 089 public static final String NAME = "documentResolver"; 090 091 public static final String PARAM_STORE = "store"; 092 093 public static final String STORE_PATH_REF = "path"; 094 095 public static final String STORE_ID_REF = "id"; 096 097 private Map<String, Serializable> parameters; 098 099 public static enum MODE { 100 PATH_REF, ID_REF; 101 } 102 103 private MODE mode = MODE.ID_REF; 104 105 public MODE getMode() { 106 return mode; 107 } 108 109 private List<Class<?>> managedClasses = null; 110 111 @Override 112 public List<Class<?>> getManagedClasses() { 113 if (managedClasses == null) { 114 managedClasses = new ArrayList<Class<?>>(); 115 managedClasses.add(DocumentModel.class); 116 } 117 return managedClasses; 118 } 119 120 @Override 121 public void configure(Map<String, String> parameters) throws IllegalStateException { 122 if (this.parameters != null) { 123 throw new IllegalStateException("cannot change configuration, may be already in use somewhere"); 124 } 125 String store = parameters.get(PARAM_STORE); 126 if (store != null) { 127 if (STORE_ID_REF.equals(store)) { 128 mode = MODE.ID_REF; 129 } else if (STORE_PATH_REF.equals(store)) { 130 mode = MODE.PATH_REF; 131 } 132 } 133 this.parameters = new HashMap<String, Serializable>(); 134 this.parameters.put(PARAM_STORE, mode == MODE.ID_REF ? STORE_ID_REF : STORE_PATH_REF); 135 } 136 137 @Override 138 public String getName() { 139 checkConfig(); 140 return NAME; 141 } 142 143 @Override 144 public Map<String, Serializable> getParameters() { 145 checkConfig(); 146 return Collections.unmodifiableMap(parameters); 147 } 148 149 @Override 150 public boolean validate(Object value) throws IllegalStateException { 151 checkConfig(); 152 if (value != null && value instanceof String) { 153 REF ref = REF.fromValue((String) value); 154 if (ref != null) { 155 try (CoreSession session = CoreInstance.openCoreSession(ref.repo)) { 156 switch (mode) { 157 case ID_REF: 158 return session.exists(new IdRef(ref.ref)); 159 case PATH_REF: 160 return session.exists(new PathRef(ref.ref)); 161 } 162 } catch (LocalException le) { // no such repo 163 return false; 164 } 165 } 166 } 167 return false; 168 } 169 170 @Override 171 public Object fetch(Object value) throws IllegalStateException { 172 checkConfig(); 173 if (value != null && value instanceof String) { 174 REF ref = REF.fromValue((String) value); 175 if (ref != null) { 176 try (CoreSession session = CoreInstance.openCoreSession(ref.repo)) { 177 try { 178 DocumentModel doc; 179 switch (mode) { 180 case ID_REF: 181 doc = session.getDocument(new IdRef(ref.ref)); 182 break; 183 case PATH_REF: 184 doc = session.getDocument(new PathRef(ref.ref)); 185 break; 186 default: 187 throw new UnsupportedOperationException(); 188 } 189 // detach because we're about to close the session 190 doc.detach(true); 191 return doc; 192 } catch (DocumentNotFoundException e) { 193 return null; 194 } 195 } catch (LocalException le) { // no such repo 196 return null; 197 } 198 } 199 } 200 return null; 201 } 202 203 @Override 204 public <T> T fetch(Class<T> type, Object value) throws IllegalStateException { 205 checkConfig(); 206 DocumentModel doc = (DocumentModel) fetch(value); 207 if (doc != null) { 208 if (type.isInstance(doc)) { 209 return type.cast(doc); 210 } 211 return doc.getAdapter(type); 212 } 213 return null; 214 } 215 216 @Override 217 public Serializable getReference(Object entity) throws IllegalStateException { 218 checkConfig(); 219 if (entity != null && entity instanceof DocumentModel) { 220 DocumentModel doc = (DocumentModel) entity; 221 String repositoryName = doc.getRepositoryName(); 222 if (repositoryName != null) { 223 switch (mode) { 224 case ID_REF: 225 return repositoryName + ":" + doc.getId(); 226 case PATH_REF: 227 return repositoryName + ":" + doc.getPath().toString(); 228 } 229 } 230 } 231 return null; 232 } 233 234 @Override 235 public String getConstraintErrorMessage(Object invalidValue, Locale locale) { 236 checkConfig(); 237 switch (mode) { 238 case ID_REF: 239 return Helper.getConstraintErrorMessage(this, "id", invalidValue, locale); 240 case PATH_REF: 241 return Helper.getConstraintErrorMessage(this, "path", invalidValue, locale); 242 default: 243 return String.format("%s cannot resolve reference %s", getName(), invalidValue); 244 } 245 } 246 247 private void checkConfig() throws IllegalStateException { 248 if (parameters == null) { 249 throw new IllegalStateException( 250 "you should call #configure(Map<String, String>) before. Please get this resolver throught ExternalReferenceService which is in charge of resolver configuration."); 251 } 252 } 253 254 protected static final class REF { 255 256 protected String repo; 257 258 protected String ref; 259 260 protected REF() { 261 } 262 263 protected static REF fromValue(String value) { 264 String[] split = value.split(":"); 265 if (split.length == 1) { 266 REF ref = new REF(); 267 ref.repo = DEFAULT_REPO_NAME; 268 ref.ref = split[0]; 269 return ref; 270 } 271 if (split.length == 2) { 272 REF ref = new REF(); 273 ref.repo = split[0]; 274 ref.ref = split[1]; 275 return ref; 276 } 277 return null; 278 } 279 280 } 281 282}