001/*
002 *  Copyright 2015 Anyware Services
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 */
016package org.ametys.plugins.repository.workspace;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import javax.jcr.RepositoryException;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.logger.AbstractLogEnabled;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.core.ui.Callable;
034import org.ametys.plugins.repository.AmetysObject;
035import org.ametys.plugins.repository.AmetysObjectIterable;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.AmetysRepositoryException;
038import org.ametys.plugins.repository.ModifiableAmetysObject;
039import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
040import org.ametys.plugins.repository.RemovableAmetysObject;
041import org.ametys.plugins.repository.UnknownAmetysObjectException;
042import org.ametys.plugins.repository.jcr.JCRAmetysObject;
043import org.ametys.plugins.repository.lock.LockAwareAmetysObject;
044import org.ametys.plugins.repository.lock.LockableAmetysObject;
045import org.ametys.plugins.repository.metadata.MetadataAwareAmetysObject;
046import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
047import org.ametys.plugins.repository.metadata.ModifiableMetadataAwareAmetysObject;
048
049/**
050 * DAO providing methods to manage {@link AmetysObject}s.
051 */
052public class AmetysObjectDao extends AbstractLogEnabled implements Component, Serviceable
053{
054    
055    /** The AmetysObject resolver. */
056    protected AmetysObjectResolver _resolver;
057    
058    @Override
059    public void service(ServiceManager serviceManager) throws ServiceException
060    {
061        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
062    }
063    
064    /**
065     * Get information on an {@link AmetysObject} from its identifier.
066     * @param id the AmetysObject identifier.
067     * @return information on the AmetysObject as a Map.
068     */
069    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
070    public Map<String, Object> getAmetysObject(String id)
071    {
072        if (getLogger().isInfoEnabled())
073        {
074            getLogger().info("Getting AmetysObject from ID '" + id + "'");
075        }
076        
077        AmetysObject obj = _resolver.resolveById(id);
078        Map<String, Object> info = new HashMap<>();
079        putAmetysObjectInfo(info, obj);
080        
081        return info;
082    }
083    
084    /**
085     * Get information on an {@link AmetysObject}, found by its path.
086     * @param path the absolute AmetysObject path (starting with /ametys:root).
087     * @return information on the AmetysObject as a Map.
088     */
089    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
090    public Map<String, Object> getAmetysObjectByPath(String path)
091    {
092        if (getLogger().isInfoEnabled())
093        {
094            getLogger().info("Getting AmetysObject at path '" + path + "'");
095        }
096        
097        AmetysObject obj = _resolver.resolveByPath(path);
098        Map<String, Object> info = new HashMap<>();
099        putAmetysObjectInfo(info, obj);
100        
101        return info;
102    }
103    
104    /**
105     * Get information on a {@link AmetysObject}s from their identifier.
106     * @param ids a Collection of AmetysObject identifiers.
107     * @return a Map containing the list of AmetysObject info ('objects' key)
108     * and the list of unknown identifiers ('notFound' key).
109     */
110    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
111    public Map<String, Object> getAmetysObjects(Collection<String> ids)
112    {
113        List<Map<String, Object>> objects = new ArrayList<>();
114        List<String> notFound = new ArrayList<>();
115        
116        for (String id : ids)
117        {
118            try
119            {
120                AmetysObject obj = null;
121                if ("/".equals(id))
122                {
123                    obj = _resolver.resolveByPath("/");
124                }
125                else
126                {
127                    obj = _resolver.resolveById(id);
128                }
129                
130                Map<String, Object> objectInfo = new HashMap<>();
131                putAmetysObjectInfo(objectInfo, obj);
132                objects.add(objectInfo);
133            }
134            catch (UnknownAmetysObjectException e)
135            {
136                notFound.add(id);
137            }
138        }
139        
140        Map<String, Object> result = new HashMap<>();
141        result.put("objects", objects);
142        result.put("notFound", notFound);
143        
144        return result;
145    }
146    
147    /**
148     * Add an AmetysObject to the repository.
149     * @param parentId the parent AmetysObject ID, must be ModifiableTraversable.
150     * @param name the name of the AmetysObject to create.
151     * @param type the AmetysObject type.
152     * @return a Map of information on the created AmetysObject.
153     */
154    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
155    public Map<String, Object> addAmetysObject(String parentId, String name, String type)
156    {
157        if (getLogger().isInfoEnabled())
158        {
159            getLogger().info("Trying to add child: '" + name + "' to the AmetysObject of id '" + parentId + "'");
160        }
161        
162        AmetysObject obj = null;
163        if (parentId.equals("/"))
164        {
165            obj = _resolver.resolveByPath("/");
166        }
167        else
168        {
169            obj = _resolver.resolveById(parentId);
170        }
171        
172        if (!(obj instanceof ModifiableTraversableAmetysObject))
173        {
174            throw new IllegalArgumentException();
175        }
176        
177        // Create child if modifiable traversable.
178        AmetysObject child = ((ModifiableTraversableAmetysObject) obj).createChild(name, type);
179        ((ModifiableTraversableAmetysObject) obj).saveChanges();
180        
181        Map<String, Object> childInfo = new HashMap<>();
182        putAmetysObjectInfo(childInfo, child);
183        
184        return childInfo;
185    }
186    
187    /**
188     * Remove an AmetysObject.
189     * @param id the identifier of the AmetysObject to remove.
190     */
191    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
192    public void removeAmetysObject(String id)
193    {
194        if (getLogger().isInfoEnabled())
195        {
196            getLogger().info("Trying to remove the AmetysObject of id '" + id + "'");
197        }
198        
199        AmetysObject obj = null;
200        
201        if (id.equals("/"))
202        {
203            obj = _resolver.resolveByPath("/");
204        }
205        else
206        {
207            obj = _resolver.resolveById(id);
208        }
209        
210        if (obj instanceof RemovableAmetysObject)
211        {
212            ModifiableAmetysObject parent = obj.getParent();
213            ((RemovableAmetysObject) obj).remove();
214            parent.saveChanges();
215        }
216    }
217    
218    /**
219     * Remove a metadata of a Ametys object
220     * @param id the identifier of the AmetysObject
221     * @param compositePath the path of the parent metadata. Can be null or empty.
222     * @param metadataName The name of metadata to remove
223     */
224    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
225    public void removeMetadata (String id, String compositePath, String metadataName)
226    {
227        AmetysObject obj = null;
228        if (id.equals("/"))
229        {
230            obj = _resolver.resolveByPath("/");
231        }
232        else
233        {
234            obj = _resolver.resolveById(id);
235        }
236        
237        if (obj instanceof ModifiableMetadataAwareAmetysObject)
238        {
239            ModifiableCompositeMetadata holder = ((ModifiableMetadataAwareAmetysObject) obj).getMetadataHolder();
240            
241            if (StringUtils.isNotEmpty(compositePath))
242            {
243                // deep search in composite metadata
244                if (!compositePath.isEmpty())
245                {
246                    String[] tokens = compositePath.split("/");        
247                    for (int i = 0; i < tokens.length; i++)
248                    {
249                        holder = holder.getCompositeMetadata(tokens[i]);
250                    }
251                }
252            }
253            
254            holder.removeMetadata(metadataName);
255            ((ModifiableMetadataAwareAmetysObject) obj).saveChanges();
256        }
257    }
258    
259    /**
260     * Unlock an AmetysObject.
261     * @param id the identifier of the AmetysObject to remove.
262     */
263    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
264    public void unlockAmetysObject(String id)
265    {
266        if (getLogger().isInfoEnabled())
267        {
268            getLogger().info("Trying to unlock the AmetysObject of id '" + id + "'");
269        }
270        
271        AmetysObject obj = null;
272        
273        if (id.equals("/"))
274        {
275            obj = _resolver.resolveByPath("/");
276        }
277        else
278        {
279            obj = _resolver.resolveById(id);
280        }
281        
282        if (obj instanceof LockableAmetysObject)
283        {
284            LockableAmetysObject lockableObject = (LockableAmetysObject) obj;
285            if (lockableObject.isLocked())
286            {
287                lockableObject.unlock();
288            }
289        }
290    }
291    
292    /**
293     * Execute a query to find AmetysObjects.
294     * @param query the query to execute.
295     * @return a List of AmetysObject info (as Maps).
296     */
297    @Callable(rights = "REPOSITORY_Rights_Access", context = "/repository")
298    public List<Map<String, Object>> query(String query)
299    {
300        if (getLogger().isInfoEnabled())
301        {
302            getLogger().info("Executing logic query: " + query);
303        }
304        
305        List<Map<String, Object>> results = new ArrayList<>();
306        
307        if (StringUtils.isNotBlank(query))
308        {
309            AmetysObjectIterable<AmetysObject> objects = _resolver.query(query);
310            for (AmetysObject object : objects)
311            {
312                Map<String, Object> result = new HashMap<>();
313                result.put("id", object.getId());
314                result.put("name", object.getName());
315                result.put("path", object.getPath());
316                
317                results.add(result);
318            }
319        }
320        
321        return results;
322    }
323    
324    /**
325     * Fill the given Map with information on an AmetysObject.
326     * @param info the Map to fill.
327     * @param obj the AmetysObject.
328     */
329    protected void putAmetysObjectInfo(Map<String, Object> info, AmetysObject obj)
330    {
331        try
332        {
333            info.put("id", obj.getId());
334            info.put("name", obj.getName());
335            info.put("path", obj.getPath());
336            
337            info.put("metadataAware", obj instanceof MetadataAwareAmetysObject);
338            info.put("modifiable", obj instanceof ModifiableAmetysObject);
339            info.put("modifiableTraversable", obj instanceof ModifiableTraversableAmetysObject);
340            
341            if (obj instanceof JCRAmetysObject)
342            {
343                info.put("jcrAo", true);
344                info.put("jcrPath", ((JCRAmetysObject) obj).getNode().getPath());
345            }
346            
347            if (obj instanceof LockAwareAmetysObject)
348            {
349                info.put("locked", ((LockAwareAmetysObject) obj).isLocked());
350                info.put("lockable", obj instanceof LockableAmetysObject);
351            }
352            else
353            {
354                info.put("lockable", false);
355            }
356        }
357        catch (RepositoryException e)
358        {
359            throw new AmetysRepositoryException("Error getting information on the object " + obj.toString(), e);
360        }
361    }
362    
363}