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.ComponentManager;
047import org.nuxeo.runtime.model.DefaultComponent;
048
049import com.google.common.cache.Cache;
050import com.google.common.cache.CacheBuilder;
051
052/**
053 * Implementation of {@code UserProfileService}.
054 *
055 * @see UserProfileService
056 * @author <a href="mailto:qlamerand@nuxeo.com">Quentin Lamerand</a>
057 * @since 5.5
058 */
059public class UserProfileServiceImpl extends DefaultComponent implements UserProfileService {
060
061    private static final Log log = LogFactory.getLog(UserProfileServiceImpl.class);
062
063    protected static final Integer CACHE_CONCURRENCY_LEVEL = 10;
064
065    protected static final Integer CACHE_TIMEOUT = 10;
066
067    protected static final Integer CACHE_MAXIMUM_SIZE = 1000;
068
069    public static final String CONFIG_EP = "config";
070
071    private ImporterConfig config;
072
073    private UserWorkspaceService userWorkspaceService;
074
075    protected final Cache<String, String> profileUidCache = CacheBuilder.newBuilder().concurrencyLevel(
076            CACHE_CONCURRENCY_LEVEL).maximumSize(CACHE_MAXIMUM_SIZE).expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES).build();
077
078    @Override
079    public DocumentModel getUserProfileDocument(CoreSession session) {
080        DocumentModel userWorkspace = getUserWorkspaceService().getCurrentUserPersonalWorkspace(session, null);
081        if (userWorkspace == null) {
082            return null;
083        }
084
085        String uid = profileUidCache.getIfPresent(session.getPrincipal().getName());
086        final IdRef ref = new IdRef(uid);
087        if (uid != null && session.exists(ref)) {
088            return session.getDocument(ref);
089        } else {
090            DocumentModel profile = new UserProfileDocumentGetter(session, userWorkspace).getOrCreate();
091            profileUidCache.put(session.getPrincipal().getName(), profile.getId());
092            return profile;
093        }
094    }
095
096    @Override
097    public DocumentModel getUserProfileDocument(String userName, CoreSession session) {
098        DocumentModel userWorkspace = getUserWorkspaceService().getUserPersonalWorkspace(userName,
099                session.getRootDocument());
100        if (userWorkspace == null) {
101            return null;
102        }
103
104        String uid = profileUidCache.getIfPresent(userName);
105        final IdRef ref = new IdRef(uid);
106        if (uid != null && session.exists(ref)) {
107            return session.getDocument(ref);
108        } else {
109            DocumentModel profile = new UserProfileDocumentGetter(session, userWorkspace).getOrCreate();
110            profileUidCache.put(userName, profile.getId());
111            return profile;
112        }
113    }
114
115    @Override
116    public DocumentModel getUserProfile(DocumentModel userModel, CoreSession session) {
117        DocumentModel userProfileDoc = getUserProfileDocument(userModel.getId(), session);
118        if (userProfileDoc == null) {
119            return null;
120        }
121
122        userProfileDoc.detach(true);
123        userProfileDoc.getDataModels().putAll(userModel.getDataModels());
124        return userProfileDoc;
125    }
126
127    private UserWorkspaceService getUserWorkspaceService() {
128        if (userWorkspaceService == null) {
129            userWorkspaceService = Framework.getService(UserWorkspaceService.class);
130        }
131        return userWorkspaceService;
132    }
133
134    private class UserProfileDocumentGetter extends UnrestrictedSessionRunner {
135
136        private DocumentModel userWorkspace;
137
138        private DocumentRef userProfileDocRef;
139
140        public UserProfileDocumentGetter(CoreSession session, DocumentModel userWorkspace) {
141            super(session);
142            this.userWorkspace = userWorkspace;
143        }
144
145        @Override
146        public void run() {
147
148            String query = "select * from " + USER_PROFILE_DOCTYPE + " where ecm:parentId='" + userWorkspace.getId()
149                    + "' " + " AND ecm:isProxy = 0 "
150                    + " AND ecm:isCheckedInVersion = 0 AND ecm:currentLifeCycleState != 'deleted'";
151            DocumentModelList children = session.query(query);
152            if (!children.isEmpty()) {
153                userProfileDocRef = children.get(0).getRef();
154            } else {
155                DocumentModel userProfileDoc = session.createDocumentModel(userWorkspace.getPathAsString(),
156                        String.valueOf(System.currentTimeMillis()), USER_PROFILE_DOCTYPE);
157                userProfileDoc = session.createDocument(userProfileDoc);
158                userProfileDocRef = userProfileDoc.getRef();
159                ACP acp = session.getACP(userProfileDocRef);
160                ACL acl = acp.getOrCreateACL();
161                acl.add(new ACE(EVERYONE, READ, true));
162                acp.addACL(acl);
163                session.setACP(userProfileDocRef, acp, true);
164                session.save();
165            }
166        }
167
168        public DocumentModel getOrCreate() {
169            if (session.hasPermission(userWorkspace.getRef(), ADD_CHILDREN)) {
170                run();
171            } else {
172                runUnrestricted();
173            }
174            return session.getDocument(userProfileDocRef);
175        }
176    }
177
178    @Override
179    public void clearCache() {
180        profileUidCache.invalidateAll();
181    }
182
183    @Override
184    public ImporterConfig getImporterConfig() {
185        return config;
186    }
187
188    @Override
189    public int getApplicationStartedOrder() {
190        return 101; // after RepositoryService
191    }
192
193    @Override
194    public void start(ComponentContext context) {
195        if (config == null || config.getDataFileName() == null) {
196            return;
197        }
198        Framework.getRuntime().getComponentManager().addListener(new ComponentManager.Listener() {
199            @Override
200            public void afterStart(ComponentManager mgr, boolean isResume) {
201                // needs to run after RepositoryInitializationHandlers, run by RepositoryService
202                scheduleImport();
203            }
204
205            @Override
206            public void afterStop(ComponentManager mgr, boolean isStandby) {
207                Framework.getRuntime().getComponentManager().removeListener(this);
208            }
209        });
210    }
211
212    protected void scheduleImport() {
213        WorkManager wm = Framework.getService(WorkManager.class);
214        if (wm != null) {
215            wm.schedule(new UserProfileImporterWork(), true);
216        }
217    }
218
219    @Override
220    public void registerContribution(Object contribution,
221            String extensionPoint, ComponentInstance contributor) {
222        if (CONFIG_EP.equals(extensionPoint)) {
223            if (config != null) {
224                log.warn("Overriding existing user profile importer config");
225            }
226            config = (ImporterConfig) contribution;
227        }
228    }
229
230    @Override
231    public void unregisterContribution(Object contribution,
232            String extensionPoint, ComponentInstance contributor) {
233        if (CONFIG_EP.equals(extensionPoint)) {
234            if (config != null && config.equals(contribution)) {
235                config = null;
236            }
237        }
238    }
239}