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