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