001/*
002 * (C) Copyright 2011 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 *     Quentin Lamerand <qlamerand@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.user.center.profile;
021
022import static org.nuxeo.ecm.core.api.security.SecurityConstants.ADD_CHILDREN;
023import static org.nuxeo.ecm.core.api.security.SecurityConstants.EVERYONE;
024import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ;
025import static org.nuxeo.ecm.user.center.profile.UserProfileConstants.USER_PROFILE_DOCTYPE;
026
027import java.util.concurrent.TimeUnit;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.ecm.core.api.CoreSession;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.DocumentModelList;
034import org.nuxeo.ecm.core.api.DocumentRef;
035import org.nuxeo.ecm.core.api.IdRef;
036import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
037import org.nuxeo.ecm.core.api.security.ACE;
038import org.nuxeo.ecm.core.api.security.ACL;
039import org.nuxeo.ecm.core.api.security.ACP;
040import org.nuxeo.ecm.core.work.api.WorkManager;
041import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling;
042import org.nuxeo.ecm.platform.userworkspace.api.UserWorkspaceService;
043import org.nuxeo.runtime.api.Framework;
044import org.nuxeo.runtime.model.ComponentContext;
045import org.nuxeo.runtime.model.ComponentInstance;
046import org.nuxeo.runtime.model.DefaultComponent;
047
048import com.google.common.cache.Cache;
049import com.google.common.cache.CacheBuilder;
050
051/**
052 * Implementation of {@code UserProfileService}.
053 *
054 * @see UserProfileService
055 * @author <a href="mailto:qlamerand@nuxeo.com">Quentin Lamerand</a>
056 * @since 5.5
057 */
058public class UserProfileServiceImpl extends DefaultComponent implements UserProfileService {
059
060    private static final Log log = LogFactory.getLog(UserProfileServiceImpl.class);
061
062    protected static final Integer CACHE_CONCURRENCY_LEVEL = 10;
063
064    protected static final Integer CACHE_TIMEOUT = 10;
065
066    protected static final Integer CACHE_MAXIMUM_SIZE = 1000;
067
068    public static final String CONFIG_EP = "config";
069
070    private ImporterConfig config;
071
072    private UserWorkspaceService userWorkspaceService;
073
074    protected final Cache<String, String> profileUidCache = CacheBuilder.newBuilder().concurrencyLevel(
075            CACHE_CONCURRENCY_LEVEL).maximumSize(CACHE_MAXIMUM_SIZE).expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES).build();
076
077    @Override
078    public DocumentModel getUserProfileDocument(CoreSession session) {
079        DocumentModel userWorkspace = getUserWorkspaceService().getCurrentUserPersonalWorkspace(session, null);
080        if (userWorkspace == null) {
081            return null;
082        }
083
084        String uid = profileUidCache.getIfPresent(session.getPrincipal().getName());
085        final IdRef ref = new IdRef(uid);
086        if (uid != null && session.exists(ref)) {
087            return session.getDocument(ref);
088        } else {
089            DocumentModel profile = new UserProfileDocumentGetter(session, userWorkspace).getOrCreate();
090            profileUidCache.put(session.getPrincipal().getName(), profile.getId());
091            return profile;
092        }
093    }
094
095    @Override
096    public DocumentModel getUserProfileDocument(String userName, CoreSession session) {
097        DocumentModel userWorkspace = getUserWorkspaceService().getUserPersonalWorkspace(userName,
098                session.getRootDocument());
099        if (userWorkspace == null) {
100            return null;
101        }
102
103        String uid = profileUidCache.getIfPresent(userName);
104        final IdRef ref = new IdRef(uid);
105        if (uid != null && session.exists(ref)) {
106            return session.getDocument(ref);
107        } else {
108            DocumentModel profile = new UserProfileDocumentGetter(session, userWorkspace).getOrCreate();
109            profileUidCache.put(userName, profile.getId());
110            return profile;
111        }
112    }
113
114    @Override
115    public DocumentModel getUserProfile(DocumentModel userModel, CoreSession session) {
116        DocumentModel userProfileDoc = getUserProfileDocument(userModel.getId(), session);
117        if (userProfileDoc == null) {
118            return null;
119        }
120
121        userProfileDoc.detach(true);
122        userProfileDoc.getDataModels().putAll(userModel.getDataModels());
123        return userProfileDoc;
124    }
125
126    private UserWorkspaceService getUserWorkspaceService() {
127        if (userWorkspaceService == null) {
128            userWorkspaceService = Framework.getLocalService(UserWorkspaceService.class);
129        }
130        return userWorkspaceService;
131    }
132
133    private class UserProfileDocumentGetter extends UnrestrictedSessionRunner {
134
135        private DocumentModel userWorkspace;
136
137        private DocumentRef userProfileDocRef;
138
139        public UserProfileDocumentGetter(CoreSession session, DocumentModel userWorkspace) {
140            super(session);
141            this.userWorkspace = userWorkspace;
142        }
143
144        @Override
145        public void run() {
146
147            String query = "select * from " + USER_PROFILE_DOCTYPE + " where ecm:parentId='" + userWorkspace.getId()
148                    + "' " + " AND ecm:isProxy = 0 "
149                    + " AND ecm:isCheckedInVersion = 0 AND ecm:currentLifeCycleState != 'deleted'";
150            DocumentModelList children = session.query(query);
151            if (!children.isEmpty()) {
152                userProfileDocRef = children.get(0).getRef();
153            } else {
154                DocumentModel userProfileDoc = session.createDocumentModel(userWorkspace.getPathAsString(),
155                        String.valueOf(System.currentTimeMillis()), USER_PROFILE_DOCTYPE);
156                userProfileDoc = session.createDocument(userProfileDoc);
157                userProfileDocRef = userProfileDoc.getRef();
158                ACP acp = session.getACP(userProfileDocRef);
159                ACL acl = acp.getOrCreateACL();
160                acl.add(new ACE(EVERYONE, READ, true));
161                acp.addACL(acl);
162                session.setACP(userProfileDocRef, acp, true);
163                session.save();
164            }
165        }
166
167        public DocumentModel getOrCreate() {
168            if (session.hasPermission(userWorkspace.getRef(), ADD_CHILDREN)) {
169                run();
170            } else {
171                runUnrestricted();
172            }
173            return session.getDocument(userProfileDocRef);
174        }
175    }
176
177    @Override
178    public void clearCache() {
179        profileUidCache.invalidateAll();
180    }
181
182    @Override
183    public ImporterConfig getImporterConfig() {
184        return config;
185    }
186
187    @Override
188    public void applicationStarted(ComponentContext context) {
189        if (config == null || config.getDataFileName() == null) {
190            return;
191        }
192        WorkManager wm = Framework.getService(WorkManager.class);
193        if (wm!=null) {
194            wm.schedule(new UserProfileImporterWork(), Scheduling.IF_NOT_RUNNING_OR_SCHEDULED, true);
195        }
196    }
197
198    @Override
199    public void registerContribution(Object contribution,
200            String extensionPoint, ComponentInstance contributor) {
201        if (CONFIG_EP.equals(extensionPoint)) {
202            if (config != null) {
203                log.warn("Overriding existing user profile importer config");
204            }
205            config = (ImporterConfig) contribution;
206        }
207    }
208
209    @Override
210    public void unregisterContribution(Object contribution,
211            String extensionPoint, ComponentInstance contributor) {
212        if (CONFIG_EP.equals(extensionPoint)) {
213            if (config != null && config.equals(contribution)) {
214                config = null;
215            }
216        }
217    }
218}