001/*
002 *  Copyright 2014 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.contentio.in;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.avalon.framework.configuration.Configurable;
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.commons.io.FilenameUtils;
036import org.apache.commons.lang3.StringUtils;
037
038import org.ametys.cms.FilterNameHelper;
039import org.ametys.cms.repository.Content;
040import org.ametys.cms.repository.ContentQueryHelper;
041import org.ametys.cms.repository.WorkflowAwareContent;
042import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
043import org.ametys.cms.workflow.ContentWorkflowHelper;
044import org.ametys.cms.workflow.EditContentFunction;
045import org.ametys.plugins.repository.AmetysObjectIterable;
046import org.ametys.plugins.repository.AmetysObjectResolver;
047import org.ametys.plugins.repository.query.expression.AndExpression;
048import org.ametys.plugins.repository.query.expression.Expression;
049import org.ametys.plugins.repository.query.expression.Expression.Operator;
050import org.ametys.plugins.repository.query.expression.StringExpression;
051import org.ametys.plugins.workflow.AbstractWorkflowComponent;
052import org.ametys.runtime.plugin.component.AbstractLogEnabled;
053
054import com.opensymphony.workflow.WorkflowException;
055
056/**
057 * Abstract {@link ContentImporter} class which provides base importer configuration and logic.<br>
058 * Configuration options:
059 * <ul>
060 *   <li>Importer priority</li>
061 *   <li>Allowed extensions, without leading dot and comma-separated</li>
062 *   <li>Content types and mixins of the created contents</li>
063 *   <li>Language of the created contents</li>
064 *   <li>Content workflow name and creation action ID</li>
065 * </ul><br>
066 * Example configuration handled by the configure method:
067 * <pre>
068 * <extension point="org.ametys.plugins.contentio.ContentImporterExtensionPoint"
069 *               id="my.content.importer"
070 *               class="...">
071 *     <priority>500</priority>
072 *     <extensions>ext,ext2</extensions>
073 *     <content-creation>
074 *         <content-types>My.ContentType.1,My.ContentType.2</content-types>
075 *         <mixins>My.Mixin.1,My.Mixin.2</mixins>
076 *         <language>en</language>
077 *         <workflow name="content" createActionId="1" editActionId="2"/>
078 *     </content-creation>
079 * </extension>
080 * </pre>
081 */
082public abstract class AbstractContentImporter extends AbstractLogEnabled implements ContentImporter, Serviceable, Configurable
083{
084    
085    /** The default importer priority. */
086    protected static final int DEFAULT_PRIORITY = 5000;
087    
088    /** Map used to store the mapping from "local" ID to content ID, when actually imported. */
089    protected static final String _CONTENT_ID_MAP_KEY = AbstractContentImporter.class.getName() + "$contentIdMap";
090    
091    /** Map used to store the content references, indexed by content and metadata path. */
092    protected static final String _CONTENT_LINK_MAP_KEY = AbstractContentImporter.class.getName() + "$contentLinkMap";
093    
094    /** Map used to store the content repeater sizes. */
095    protected static final String _CONTENT_REPEATER_SIZE_MAP = AbstractContentImporter.class.getName() + "$contentRepeaterSizeMap";
096    
097    /** The AmetysObject resolver. */
098    protected AmetysObjectResolver _resolver;
099    
100    /** The content workflow helper. */
101    protected ContentWorkflowHelper _contentWorkflowHelper;
102    
103    /** The importer priority. */
104    protected int _priority = DEFAULT_PRIORITY;
105    
106    /** The allowed extensions. */
107    protected Set<String> _extensions;
108    
109    /** The imported contents' types. */
110    protected String[] _contentTypes;
111    
112    /** The imported contents' mixins. */
113    protected String[] _mixins;
114    
115    /** The importer contents' language. */
116    protected String _language;
117    
118    /** The importer contents' workflow name. */
119    protected String _workflowName;
120    
121    /** The importer contents' initial action ID. */
122    protected int _initialActionId;
123    
124    /** The importer contents' edition action ID. */
125    protected int _editActionId;
126    
127    @Override
128    public void service(ServiceManager manager) throws ServiceException
129    {
130        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
131        _contentWorkflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE);
132    }
133    
134    @Override
135    public void configure(Configuration configuration) throws ConfigurationException
136    {
137        _priority = configuration.getChild("priority").getValueAsInteger(DEFAULT_PRIORITY);
138        
139        configureExtensions(configuration.getChild("extensions"));
140        
141        configureContentCreation(configuration.getChild("content-creation"));
142    }
143    
144    /**
145     * Configure the allowed extensions.
146     * @param configuration the extension configuration.
147     * @throws ConfigurationException if an error occurs.
148     */
149    protected void configureExtensions(Configuration configuration) throws ConfigurationException
150    {
151        _extensions = new HashSet<>();
152        
153        String extensionsStr = configuration.getValue("");
154        
155        if (StringUtils.isBlank(extensionsStr))
156        {
157            _extensions.addAll(getDefaultExtensions());
158        }
159        else
160        {
161            for (String ext : StringUtils.split(extensionsStr, ", "))
162            {
163                String extension = ext.trim();
164                if (extension.startsWith("."))
165                {
166                    extension = extension.substring(1);
167                }
168                
169                _extensions.add(extension);
170            }
171        }
172    }
173    
174    /**
175     * Configure the content creation parameters.
176     * @param configuration the content creation configuration.
177     * @throws ConfigurationException if an error occurs.
178     */
179    protected void configureContentCreation(Configuration configuration) throws ConfigurationException
180    {
181        String typesStr = configuration.getChild("content-types").getValue();
182        _contentTypes = StringUtils.split(typesStr, ", ");
183        
184        String mixins = configuration.getChild("mixins").getValue("");  // mixins can be empty
185        _mixins = StringUtils.split(mixins, ", ");
186        
187        _language = configuration.getChild("language").getValue();
188        
189        configureWorkflow(configuration);
190    }
191    
192    /**
193     * Configure the content workflow.
194     * @param configuration the content creation configuration.
195     * @throws ConfigurationException if an error occurs.
196     */
197    protected void configureWorkflow(Configuration configuration) throws ConfigurationException
198    {
199        Configuration wfConf = configuration.getChild("workflow");
200        
201        _workflowName = wfConf.getAttribute("name");
202        
203        _initialActionId = wfConf.getAttributeAsInteger("createActionId");
204        _editActionId = wfConf.getAttributeAsInteger("editActionId");
205    }
206    
207    @Override
208    public int getPriority()
209    {
210        return _priority;
211    }
212    
213    /**
214     * Get the default allowed extensions.
215     * @return the default allowed extensions, without leading dots. Cannot be null.
216     */
217    protected Collection<String> getDefaultExtensions()
218    {
219        return Collections.emptySet();
220    }
221    
222    /**
223     * Test if the given filename has a supported extension.
224     * @param name the name, can't be null.
225     * @return true if the extension is supported, false otherwise.
226     * @throws IOException if an error occurs.
227     */
228    protected boolean isExtensionValid(String name) throws IOException
229    {
230        return _extensions.isEmpty() || _extensions.contains(FilenameUtils.getExtension(name));
231    }
232    
233    /**
234     * The content types of a created content.
235     * @param params the import parameters.
236     * @return the content types of a created content.
237     */
238    protected String[] getContentTypes(Map<String, Object> params)
239    {
240        return _contentTypes;
241    }
242    
243    /**
244     * The mixins of a created content.
245     * @param params the import parameters.
246     * @return The mixins of a created content.
247     */
248    protected String[] getMixins(Map<String, Object> params)
249    {
250        return _mixins;
251    }
252    
253    /**
254     * The language of a created content.
255     * @param params the import parameters.
256     * @return The language of a created content.
257     */
258    protected String getLanguage(Map<String, Object> params)
259    {
260        return _language;
261    }
262    
263    /**
264     * The workflow name of a created content.
265     * @param params the import parameters.
266     * @return The workflow name of a created content.
267     */
268    protected String getWorkflowName(Map<String, Object> params)
269    {
270        return _workflowName;
271    }
272    
273    /**
274     * The workflow creation action ID of a created content.
275     * @param params the import parameters.
276     * @return The workflow creation action ID of a created content.
277     */
278    protected int getInitialActionId(Map<String, Object> params)
279    {
280        return _initialActionId;
281    }
282    
283    /**
284     * The workflow action ID used to edit a content.
285     * @param params the import parameters.
286     * @return The workflow action ID used to edit a content.
287     */
288    protected int getEditActionId(Map<String, Object> params)
289    {
290        return _editActionId;
291    }
292    
293    /**
294     * Get the map used to store the mapping from "local" ID (defined in the import file)
295     * to the AmetysObject ID of the contents, when actually imported.
296     * @param params the import parameters.
297     * @return the content "local to repository" ID map.
298     */
299    protected Map<String, String> getContentIdMap(Map<String, Object> params)
300    {
301        // Get or create the map in the global parameters.
302        @SuppressWarnings("unchecked")
303        Map<String, String> contentIdMap = (Map<String, String>) params.get(_CONTENT_ID_MAP_KEY);
304        if (contentIdMap == null)
305        {
306            contentIdMap = new HashMap<>();
307            params.put(_CONTENT_ID_MAP_KEY, contentIdMap);
308        }
309        
310        return contentIdMap;
311    }
312    
313    /**
314     * Get the map used to store the content references.
315     * The Map is shaped like: referencing content -&gt; local metadata path -&gt; content references.
316     * @param params the import parameters.
317     * @return the content reference map.
318     */
319    protected Map<Content, Map<String, Object>> getContentRefMap(Map<String, Object> params)
320    {
321        // Get or create the map in the global parameters.
322        @SuppressWarnings("unchecked")
323        Map<Content, Map<String, Object>> contentRefMap = (Map<Content, Map<String, Object>>) params.get(_CONTENT_LINK_MAP_KEY);
324        if (contentRefMap == null)
325        {
326            contentRefMap = new HashMap<>();
327            params.put(_CONTENT_LINK_MAP_KEY, contentRefMap);
328        }
329        
330        return contentRefMap;
331    }
332    
333    /**
334     * Add a content reference to the map.
335     * @param content The referencing content.
336     * @param metadataPath The path of the metadata which holds the content references.
337     * @param reference The content reference.
338     * @param params The import parameters.
339     */
340    protected void addContentReference(Content content, String metadataPath, ContentReference reference, Map<String, Object> params)
341    {
342        addContentReference(getContentRefMap(params), content, metadataPath, reference);
343    }
344    
345    /**
346     * Add a content reference to the map.
347     * @param contentRefMap The content reference map.
348     * @param content The referencing content.
349     * @param metadataPath The path of the metadata which holds the content references.
350     * @param reference The content reference.
351     */
352    protected void addContentReference(Map<Content, Map<String, Object>> contentRefMap, Content content, String metadataPath, ContentReference reference)
353    {
354        Map<String, Object> contentReferences;
355        if (contentRefMap.containsKey(content))
356        {
357            contentReferences = contentRefMap.get(content);
358        }
359        else
360        {
361            contentReferences = new HashMap<>();
362            contentRefMap.put(content, contentReferences);
363        }
364        
365        contentReferences.put(metadataPath, reference);
366    }
367    
368    /**
369     * Add content references to the map.
370     * @param contentRefMap The content reference map.
371     * @param content The referencing content.
372     * @param metadataPath The path of the metadata which holds the content references.
373     * @param references the content reference list.
374     */
375    protected void addContentReferences(Map<Content, Map<String, Object>> contentRefMap, Content content, String metadataPath, List<ContentReference> references)
376    {
377        Map<String, Object> contentReferences;
378        if (contentRefMap.containsKey(content))
379        {
380            contentReferences = contentRefMap.get(content);
381        }
382        else
383        {
384            contentReferences = new HashMap<>();
385            contentRefMap.put(content, contentReferences);
386        }
387        
388        contentReferences.put(metadataPath, references);
389    }
390    
391    /**
392     * Get the map used to store the repeater sizes.
393     * The Map is shaped like: referencing content -&gt; local metadata path -&gt; content references.
394     * @param params the import parameters.
395     * @return the content reference map.
396     */
397    protected Map<Content, Map<String, Integer>> getContentRepeaterSizeMap(Map<String, Object> params)
398    {
399        // Get or create the map in the global parameters.
400        @SuppressWarnings("unchecked")
401        Map<Content, Map<String, Integer>> contentRepeaterSizeMap = (Map<Content, Map<String, Integer>>) params.get(_CONTENT_REPEATER_SIZE_MAP);
402        if (contentRepeaterSizeMap == null)
403        {
404            contentRepeaterSizeMap = new HashMap<>();
405            params.put(_CONTENT_REPEATER_SIZE_MAP, contentRepeaterSizeMap);
406        }
407        
408        return contentRepeaterSizeMap;
409    }
410    
411    /**
412     * Set a repeater size in the map (needed to execute the edit content function).
413     * @param content The content containing the repeater.
414     * @param metadataPath The repeater metadata path.
415     * @param repeaterSize The repeater size.
416     * @param params The import parameters.
417     */
418    protected void setRepeaterSize(Content content, String metadataPath, int repeaterSize, Map<String, Object> params)
419    {
420        Map<Content, Map<String, Integer>> contentRepeaterSizeMap = getContentRepeaterSizeMap(params);
421        
422        Map<String, Integer> repeaters;
423        if (contentRepeaterSizeMap.containsKey(content))
424        {
425            repeaters = contentRepeaterSizeMap.get(content);
426        }
427        else
428        {
429            repeaters = new HashMap<>();
430            contentRepeaterSizeMap.put(content, repeaters);
431        }
432        
433        repeaters.put(metadataPath, repeaterSize);
434    }
435    
436    /**
437     * Create a content.
438     * @param title the content title.
439     * @param params the import parameters.
440     * @return the created content.
441     * @throws WorkflowException if an error occurs.
442     */
443    protected Content createContent(String title, Map<String, Object> params) throws WorkflowException
444    {
445        String[] contentTypes = getContentTypes(params);
446        String[] mixins = getMixins(params);
447        String language = getLanguage(params);
448        String workflowName = getWorkflowName(params);
449        int initialActionId = getInitialActionId(params);
450        
451        return createContent(title, contentTypes, mixins, language, workflowName, initialActionId, params);
452    }
453    
454    /**
455     * Create a content.
456     * @param title the content title.
457     * @param contentTypes the content types.
458     * @param mixins the content mixins.
459     * @param language the content language.
460     * @param params the import parameters.
461     * @return the created content.
462     * @throws WorkflowException if an error occurs.
463     */
464    protected Content createContent(String title, String[] contentTypes, String[] mixins, String language, Map<String, Object> params) throws WorkflowException
465    {
466        String workflowName = getWorkflowName(params);
467        int initialActionId = getInitialActionId(params);
468        
469        return createContent(title, contentTypes, mixins, language, workflowName, initialActionId, params);
470    }
471    
472    /**
473     * Create a content.
474     * @param title the content title.
475     * @param contentTypes the content types.
476     * @param mixins the content mixins.
477     * @param language the content language.
478     * @param parentContentId the parent content ID.
479     * @param parentContentMetadataPath the parent content metadata path.
480     * @param params the import parameters.
481     * @return the created content.
482     * @throws WorkflowException if an error occurs.
483     */
484    protected Content createContent(String title, String[] contentTypes, String[] mixins, String language, String parentContentId, String parentContentMetadataPath, Map<String, Object> params) throws WorkflowException
485    {
486        String workflowName = getWorkflowName(params);
487        int initialActionId = getInitialActionId(params);
488        
489        return createContent(title, contentTypes, mixins, language, workflowName, initialActionId, parentContentId, parentContentMetadataPath, params);
490    }
491    
492    /**
493     * Create a content.
494     * @param title the content title.
495     * @param contentTypes the content types.
496     * @param mixins the content mixins.
497     * @param language the content language.
498     * @param workflowName the content workflow name.
499     * @param initialActionId the content create action ID.
500     * @param params the import parameters.
501     * @return the created content.
502     * @throws WorkflowException if an error occurs.
503     */
504    protected Content createContent(String title, String[] contentTypes, String[] mixins, String language, String workflowName, int initialActionId, Map<String, Object> params) throws WorkflowException
505    {
506        return createContent(title, contentTypes, mixins, language, workflowName, initialActionId, null, null, params);
507    }
508    
509    /**
510     * Create a content.
511     * @param title the content title.
512     * @param contentTypes the content types.
513     * @param mixins the content mixins.
514     * @param language the content language.
515     * @param workflowName the content workflow name.
516     * @param initialActionId the content create action ID.
517     * @param parentContentId the parent content ID.
518     * @param parentContentMetadataPath the parent content metadata path.
519     * @param params the import parameters.
520     * @return the created content.
521     * @throws WorkflowException if an error occurs.
522     */
523    protected Content createContent(String title, String[] contentTypes, String[] mixins, String language, String workflowName, int initialActionId, String parentContentId, String parentContentMetadataPath, Map<String, Object> params) throws WorkflowException
524    {
525        String name;
526        try
527        {
528            name = FilterNameHelper.filterName(title);
529        }
530        catch (Exception e)
531        {
532            // Ignore the exception, just provide a valid start.
533            name = "content-" + title;
534        }
535        
536        Map<String, Object> result = _contentWorkflowHelper.createContent(workflowName, initialActionId, name, title, contentTypes, mixins, language, parentContentId, parentContentMetadataPath);
537        
538        return (Content) result.get(AbstractContentWorkflowComponent.CONTENT_KEY);
539    }
540    
541    /**
542     * Restore content references.
543     * @param params The import parameters.
544     */
545    protected void restoreContentReferences(Map<String, Object> params)
546    {
547        Map<Content, Map<String, Object>> contentRefMap = getContentRefMap(params);
548        Map<Content, Map<String, Integer>> contentRepeaterSizeMap = getContentRepeaterSizeMap(params);
549        int editActionId = getEditActionId(params);
550        
551        for (Content content : contentRefMap.keySet())
552        {
553            if (content instanceof WorkflowAwareContent)
554            {
555                Map<String, Object> contentReferences = contentRefMap.get(content);
556                Map<String, Integer> repeaters = contentRepeaterSizeMap.get(content);
557                
558                Map<String, Object> values = new HashMap<>();
559                
560                // Fill the value map with the content references.
561                setReferenceMetadatas(contentReferences, values, repeaters, params);
562                
563                try
564                {
565                    if (!values.isEmpty())
566                    {
567                        Map<String, Object> contextParameters = new HashMap<>();
568                        contextParameters.put(EditContentFunction.QUIT, true);
569                        contextParameters.put(EditContentFunction.FORM_RAW_VALUES, values);
570                        
571                        Map<String, Object> inputs = new HashMap<>();
572                        inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters);
573                        inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content);
574                        
575                        _contentWorkflowHelper.doAction((WorkflowAwareContent) content, editActionId, inputs);
576                    }
577                }
578                catch (WorkflowException e)
579                {
580                    // TODO Throw exception?
581                    getLogger().warn("An error occurred restoring content references in content {}", content, e);
582                }
583            }
584        }
585    }
586    
587    /**
588     * Fill the value map with the content references.
589     * @param contentReferences The list of content references indexed by metadata path.
590     * @param values The value map passed to the EditContentFunction class.
591     * @param repeaters The repeater sizes for this content.
592     * @param params The import parameters.
593     */
594    protected void setReferenceMetadatas(Map<String, Object> contentReferences, Map<String, Object> values, Map<String, Integer> repeaters, Map<String, Object> params)
595    {
596        for (String metadataPath : contentReferences.keySet())
597        {
598            Object value = contentReferences.get(metadataPath);
599            String metaKey = EditContentFunction.FORM_ELEMENTS_PREFIX + metadataPath;
600            
601            if (value instanceof List<?>)
602            {
603                // Multiple value
604                @SuppressWarnings("unchecked")
605                List<ContentReference> references = (List<ContentReference>) value;
606                List<String> contentIds = new ArrayList<>(references.size());
607                for (ContentReference reference : references)
608                {
609                    String refContentId = getReferencedContentId(reference, params);
610                    if (refContentId != null)
611                    {
612                        contentIds.add(refContentId);
613                    }
614                }
615                
616                if (!contentIds.isEmpty())
617                {
618                    values.put(metaKey, contentIds);
619                }
620            }
621            else if (value instanceof ContentReference)
622            {
623                // Single value.
624                String refContentId = getReferencedContentId((ContentReference) value, params);
625                if (refContentId != null)
626                {
627                    values.put(metaKey, refContentId);
628                }
629            }
630        }
631        
632        if (repeaters != null)
633        {
634            for (String repeaterPath : repeaters.keySet())
635            {
636                Integer size = repeaters.get(repeaterPath);
637                if (size > 0)
638                {
639                    String sizeKey = EditContentFunction.INTERNAL_FORM_ELEMENTS_PREFIX + repeaterPath + "/size";
640                    values.put(sizeKey, repeaters.get(repeaterPath).toString());
641                }
642            }
643        }
644    }
645    
646    /**
647     * Get the content ID from a content reference.
648     * @param contentRef The content reference.
649     * @param params The import parameters.
650     * @return the content ID if it was found, or null otherwise.
651     */
652    protected String getReferencedContentId(ContentReference contentRef, Map<String, Object> params)
653    {
654        int refType = contentRef.getType();
655        if (refType == ContentReference.TYPE_LOCAL_ID)
656        {
657            String localId = (String) contentRef.getValue();
658            String contentId = getContentIdMap(params).get(localId);
659            if (StringUtils.isNotEmpty(contentId) && _resolver.hasAmetysObjectForId(contentId))
660            {
661                return contentId;
662            }
663        }
664        else if (refType == ContentReference.TYPE_CONTENT_ID)
665        {
666            String contentId = (String) contentRef.getValue();
667            if (StringUtils.isNotEmpty(contentId) && _resolver.hasAmetysObjectForId(contentId))
668            {
669                return contentId;
670            }
671        }
672        else if (refType == ContentReference.TYPE_CONTENT_VALUES)
673        {
674            @SuppressWarnings("unchecked")
675            Map<String, String> values = (Map<String, String>) contentRef.getValue();
676            Content content = getContentFromProperties(values);
677            if (content != null)
678            {
679                return content.getId();
680            }
681        }
682        
683        return null;
684    }
685    
686    /**
687     * Search a content from a map of its metadata values.
688     * @param propertyValues The metadata values.
689     * @return The Content if found, null otherwise.
690     */
691    protected Content getContentFromProperties(Map<String, String> propertyValues)
692    {
693        Content content = null;
694        
695        List<Expression> expressions = new ArrayList<>();
696        for (String property : propertyValues.keySet())
697        {
698            String value = propertyValues.get(property);
699            expressions.add(new StringExpression(property, Operator.EQ, value)); 
700        }
701        
702        Expression[] exprArray = expressions.toArray(new Expression[expressions.size()]);
703        
704        String query = ContentQueryHelper.getContentXPathQuery(new AndExpression(exprArray));
705        
706        AmetysObjectIterable<Content> contents = _resolver.query(query);
707        Iterator<Content> it = contents.iterator();
708        
709        if (it.hasNext())
710        {
711            content = it.next();
712            
713            if (it.hasNext())
714            {
715                content = null;
716            }
717        }
718        
719        return content;
720    }
721    
722    /**
723     * Class representing a reference to a content in an import file.
724     */
725    public class ContentReference
726    {
727        /**
728         * The referenced content doesn't exist in the repository, it's in the import file.
729         * The reference value is the content ID in the import file.
730         */
731        public static final int TYPE_LOCAL_ID = 1;
732        
733        /**
734         * The referenced content exits in the repository and its ID is known.
735         * The reference value is the content ID in the repository (AmetysObject ID).
736         */
737        public static final int TYPE_CONTENT_ID = 2;
738        
739        /**
740         * The referenced content exits in the repository. Its ID is not known,
741         * but it can be identified by one or several of its metadata.
742         * The reference value is a Map of metadata name -&gt; value.
743         */
744        public static final int TYPE_CONTENT_VALUES = 3;
745        
746        /** The reference type. */
747        private int _type;
748        
749        /** The reference value, depends on the reference type. */
750        private Object _value;
751        
752        /**
753         * Build a content reference.
754         * @param type the reference type.
755         * @param value the reference value.
756         */
757        public ContentReference(int type, Object value)
758        {
759            this._type = type;
760            this._value = value;
761        }
762
763        /**
764         * Get the type.
765         * @return the type
766         */
767        public int getType()
768        {
769            return _type;
770        }
771
772        /**
773         * Set the type.
774         * @param type the type to set
775         */
776        public void setType(int type)
777        {
778            this._type = type;
779        }
780
781        /**
782         * Get the value.
783         * @return the value
784         */
785        public Object getValue()
786        {
787            return _value;
788        }
789
790        /**
791         * Set the value.
792         * @param value the value to set
793         */
794        public void setValue(Object value)
795        {
796            this._value = value;
797        }
798    }
799}