001/*
002 * (C) Copyright 2006-2017 Nuxeo (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 *     George Lefter
018 *     Olivier Grisel
019 *     Benjamin Jalon
020 *     Florent Guillaume
021 */
022package org.nuxeo.ecm.directory;
023
024import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
025import static org.apache.commons.lang3.StringUtils.isBlank;
026import static org.nuxeo.ecm.directory.BaseDirectoryDescriptor.DATA_LOADING_POLICY_NEVER_LOAD;
027import static org.nuxeo.ecm.directory.localconfiguration.DirectoryConfigurationConstants.DIRECTORY_CONFIGURATION_FACET;
028
029import java.time.Duration;
030import java.util.List;
031
032import org.apache.logging.log4j.LogManager;
033import org.apache.logging.log4j.Logger;
034import org.nuxeo.common.utils.DurationUtils;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.core.api.localconfiguration.LocalConfigurationService;
038import org.nuxeo.ecm.directory.api.DirectoryService;
039import org.nuxeo.ecm.directory.localconfiguration.DirectoryConfiguration;
040import org.nuxeo.runtime.api.Framework;
041import org.nuxeo.runtime.cluster.ClusterService;
042import org.nuxeo.runtime.model.ComponentContext;
043import org.nuxeo.runtime.model.ComponentInstance;
044import org.nuxeo.runtime.model.DefaultComponent;
045
046public class DirectoryServiceImpl extends DefaultComponent implements DirectoryService {
047
048    /** @since 11.1 */
049    public static final String CLUSTER_START_DURATION_PROP = "org.nuxeo.directory.cluster.start.duration";
050
051    /** @since 11.1 */
052    public static final Duration CLUSTER_START_DURATION_DEFAULT = Duration.ofMinutes(1);
053
054    protected static final String DELIMITER_BETWEEN_DIRECTORY_NAME_AND_SUFFIX = "_";
055
056    private static final Logger log = LogManager.getLogger(DirectoryServiceImpl.class);
057
058    protected DirectoryRegistry registry = new DirectoryRegistry();
059
060    @Override
061    public void activate(ComponentContext context) {
062    }
063
064    @Override
065    public void deactivate(ComponentContext context) {
066        registry.shutdown();
067    }
068
069    @Override
070    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
071        DirectoryFactoryDescriptor factoryDescriptor = (DirectoryFactoryDescriptor) contribution;
072        String factoryName = factoryDescriptor.getFactoryName();
073        log.warn("No need to register factoryDescriptor anymore: {}", factoryName);
074    }
075
076    @Override
077    public void registerDirectoryDescriptor(BaseDirectoryDescriptor descriptor) {
078        registry.addContribution(descriptor);
079    }
080
081    @Override
082    public void unregisterDirectoryDescriptor(BaseDirectoryDescriptor descriptor) {
083        registry.removeContribution(descriptor);
084    }
085
086    @Override
087    public void loadFromCSV(String directoryName, Blob dataBlob, String dataLoadingPolicy) {
088        if (isBlank(dataLoadingPolicy) || DATA_LOADING_POLICY_NEVER_LOAD.equals(dataLoadingPolicy)) {
089            throw new DirectoryException("Illegal dataLoadingPolicy: " + dataLoadingPolicy, SC_BAD_REQUEST);
090        }
091        Directory directory = getDirectoryOrFail(directoryName);
092        directory.loadFromCSV(dataBlob, dataLoadingPolicy);
093    }
094
095    @Override
096    public int getApplicationStartedOrder() {
097        // earlier than the repository init, which has order 100,
098        // but later than the cache service, which has order 95 (100-5)
099        return 97;
100    }
101
102    @Override
103    public void start(ComponentContext context) {
104        ClusterService clusterService = Framework.getService(ClusterService.class);
105        String prop = Framework.getProperty(CLUSTER_START_DURATION_PROP);
106        Duration duration = DurationUtils.parsePositive(prop, CLUSTER_START_DURATION_DEFAULT);
107        Duration pollDelay = Duration.ofSeconds(1);
108        clusterService.runAtomically("start-directories", duration, pollDelay, this::start);
109    }
110
111    protected void start() {
112        List<Directory> directories = getDirectories();
113        directories.forEach(Directory::initialize);
114        directories.forEach(Directory::initializeReferences);
115        directories.forEach(Directory::initializeInverseReferences);
116    }
117
118    protected DirectoryConfiguration getDirectoryConfiguration(DocumentModel documentContext) {
119        LocalConfigurationService localConfigurationService = Framework.getService(LocalConfigurationService.class);
120
121        if (localConfigurationService == null) {
122            log.info("Local configuration not deployed, will use default configuration");
123            return null;
124        }
125
126        return localConfigurationService.getConfiguration(DirectoryConfiguration.class, DIRECTORY_CONFIGURATION_FACET,
127                documentContext);
128    }
129
130    /**
131     * This will return the local directory name according the local configuration. If the local configuration is null
132     * or the suffix value is null or the suffix value trimmed is an empty string the returned value is the
133     * directoryName given in parameter. If not this is directoryName + DELIMITER_BETWEEN_DIRECTORY_NAME_AND_SUFFIX +
134     * suffix. if directoryName is null, return null.
135     */
136    protected String getWaitingLocalDirectoryName(String directoryName, DirectoryConfiguration configuration) {
137        if (directoryName == null) {
138            return null;
139        }
140
141        if (configuration != null && configuration.getDirectorySuffix() != null) {
142            String suffix = configuration.getDirectorySuffix().trim();
143            if (!"".equals(suffix)) {
144                return directoryName + DELIMITER_BETWEEN_DIRECTORY_NAME_AND_SUFFIX + suffix;
145            }
146            log.warn("The local configuration detected is an empty value, we consider it as no configuration set.");
147            log.debug("Directory Local Configuration is on : {}", configuration::getDocumentRef);
148        }
149
150        return directoryName;
151    }
152
153    @Override
154    public BaseDirectoryDescriptor getDirectoryDescriptor(String id) {
155        return registry.getDirectoryDescriptor(id);
156    }
157
158    @Override
159    public Directory getDirectory(String id) {
160        if (id == null) {
161            // TODO throw an exception
162            return null;
163        }
164        return registry.getDirectory(id);
165    }
166
167    @Override
168    public Directory getDirectory(String id, DocumentModel documentContext) {
169        if (id == null) {
170            // TODO throw an exception
171            return null;
172        }
173        String localDirectoryName = getWaitingLocalDirectoryName(id, getDirectoryConfiguration(documentContext));
174        Directory dir = getDirectory(localDirectoryName);
175        if (dir == null && !id.equals(localDirectoryName)) {
176            log.debug("The local directory named '{}' was not found. Look for the default one named: {}",
177                    localDirectoryName, id);
178            dir = getDirectory(id);
179        }
180        return dir;
181    }
182
183    protected Directory getDirectoryOrFail(String name) {
184        return getDirectoryOrFail(name, null);
185    }
186
187    protected Directory getDirectoryOrFail(String id, DocumentModel documentContext) {
188        Directory dir = getDirectory(id, documentContext);
189        if (dir == null) {
190            throw new DirectoryException("No directory registered with name: " + id);
191        }
192        return dir;
193    }
194
195    @Override
196    public List<Directory> getDirectories() {
197        return registry.getDirectories();
198    }
199
200    @Override
201    public List<String> getDirectoryNames() {
202        return registry.getDirectoryIds();
203    }
204
205    @Override
206    public String getDirectorySchema(String directoryName) {
207        return getDirectoryOrFail(directoryName).getSchema();
208    }
209
210    @Override
211    public String getDirectoryIdField(String directoryName) {
212        return getDirectoryOrFail(directoryName).getIdField();
213    }
214
215    @Override
216    public String getDirectoryPasswordField(String directoryName) {
217        return getDirectoryOrFail(directoryName).getPasswordField();
218    }
219
220    @Override
221    public String getParentDirectoryName(String directoryName) {
222        return getDirectoryOrFail(directoryName).getParentDirectory();
223    }
224
225    @Override
226    public Session open(String directoryName) {
227        return getDirectoryOrFail(directoryName).getSession();
228    }
229
230    @Override
231    public Session open(String directoryName, DocumentModel documentContext) {
232        return getDirectoryOrFail(directoryName, documentContext).getSession();
233    }
234
235}