View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.orcid.works;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.stream.Stream;
30  
31  import org.jdom2.Element;
32  import org.jdom2.JDOMException;
33  import org.mycore.common.MCRConstants;
34  import org.mycore.datamodel.metadata.MCRMetadataManager;
35  import org.mycore.datamodel.metadata.MCRObject;
36  import org.mycore.datamodel.metadata.MCRObjectID;
37  import org.mycore.mods.MCRMODSWrapper;
38  import org.mycore.orcid.MCRORCIDException;
39  import org.mycore.orcid.MCRORCIDProfile;
40  import org.xml.sax.SAXException;
41  
42  /**
43   * Represents the "works" section of an ORCID profile with grouped works
44   *
45   * @author Frank L\u00FCtzenkirchen
46   */
47  public class MCRWorksSection {
48  
49      private MCRORCIDProfile orcid;
50  
51      /** The groups of works this ORCID profile contains */
52      private List<MCRGroupOfWorks> groups = new ArrayList<>();
53  
54      /** All works (not grouped) */
55      private List<MCRWork> works = new ArrayList<>();
56  
57      /** Lookup table to get work by it's put code */
58      private Map<String, MCRWork> putCode2Work = new HashMap<>();
59  
60      /**
61       * Creates a representation of the ORCID's works section and
62       * fetches the grouping of works and the work summaries
63       */
64      public MCRWorksSection(MCRORCIDProfile orcid) throws JDOMException, IOException, SAXException {
65          this.orcid = orcid;
66          refetchGroupsAndSummaries();
67      }
68  
69      public MCRORCIDProfile getORCID() {
70          return orcid;
71      }
72  
73      public List<MCRWork> getWorks() {
74          return new ArrayList<>(works);
75      }
76  
77      void addWork(MCRWork work) {
78          works.add(work);
79          putCode2Work.put(work.getPutCode(), work);
80      }
81  
82      void removeWork(MCRWork work) {
83          works.remove(work);
84          putCode2Work.remove(work.getPutCode());
85      }
86  
87      /** Returns the work with the given put code, if any */
88      public MCRWork getWork(String putCode) {
89          return putCode2Work.get(putCode);
90      }
91  
92      /**
93       * Returns the list of grouped works after fetching work summaries.
94       * Multiple works from different sources which are assumed to represent the same publication
95       * are grouped together by ORCID.
96       */
97      public List<MCRGroupOfWorks> getGroups() {
98          return groups;
99      }
100 
101     /**
102      * Returns a mods:modsCollection containing all MODS representations of the works.
103      * The MODS from multiple works within the same groups is merged together,
104      * so for each group of works there will be a single mods within the collection.
105      */
106     public Element buildMODSCollection() {
107         Element modsCollection = new Element("modsCollection", MCRConstants.MODS_NAMESPACE);
108         groups.forEach(g -> modsCollection.addContent(g.buildMergedMODS()));
109         return modsCollection;
110     }
111 
112     public Element buildUnmergedMODSCollection() {
113         Element modsCollection = new Element("modsCollection", MCRConstants.MODS_NAMESPACE);
114         groups.forEach(g -> modsCollection.addContent(g.buildUnmergedMODS()));
115         return modsCollection;
116     }
117 
118     /**
119      * Fetches the grouping of works and all work summaries from the ORCID profile.
120      * Can be called to refresh information on grouping to find out how grouping of works
121      * may have changed after adding or deleting works.
122      */
123     public void refetchGroupsAndSummaries() throws JDOMException, IOException, SAXException {
124         groups = orcid.getFetcher().fetchGroups(this);
125 
126         // Now, rebuild putCode2Work and works list from groups list:
127         putCode2Work.clear();
128         works.clear();
129 
130         groups.stream().flatMap(g -> g.getWorks().stream()).forEach(work -> {
131             putCode2Work.put(work.getPutCode(), work);
132             works.add(work);
133         });
134     }
135 
136     /** Fetches the work details for all work summaries from the ORCID profile. */
137     public void fetchDetails() throws IOException, JDOMException, SAXException {
138         orcid.getFetcher().fetchDetails(this);
139     }
140 
141     /**
142      * Adds a new "work" to the remote ORCID profile.
143      * The publication data is taken from the MODS stored in the MyCoRe object with the given ID.
144      */
145     public MCRWork addWorkFrom(MCRObjectID objectID) throws IOException, JDOMException, SAXException {
146         if (!MCRMetadataManager.exists(objectID)) {
147             throw new MCRORCIDException("can not create work, object " + objectID + " does not exist locally");
148         }
149 
150         MCRWork work = orcid.getPublisher().createWorkFrom(objectID);
151         this.addWork(work);
152         return work;
153     }
154 
155     /**
156      * Returns the work originating from the given local object, if any.
157      * This is done by comparing the ID and all mods:identifier elements given in the MyCoRe MODS object
158      * with the identifiers given in the ORCID work.
159      */
160     public Optional<MCRWork> findWork(MCRObjectID oid) {
161         MCRObject obj = MCRMetadataManager.retrieveMCRObject(oid);
162         return findWork(obj);
163     }
164 
165     public Optional<MCRWork> findWork(MCRObject obj) {
166         return findWorks(obj).findFirst();
167     }
168 
169     public Optional<MCRWork> findOwnWork(MCRObjectID oid) {
170         MCRObject obj = MCRMetadataManager.retrieveMCRObject(oid);
171         return findOwnWork(obj);
172     }
173 
174     public Optional<MCRWork> findOwnWork(MCRObject obj) {
175         return findWorks(obj).filter(work -> work.getSource().isThisApplication()).findFirst();
176     }
177 
178     public Stream<MCRWork> findWorks(MCRObject obj) {
179         MCRMODSWrapper wrapper = new MCRMODSWrapper(obj);
180         List<Element> objectIdentifiers = wrapper.getElements("mods:identifier");
181         Set<String> objectKeys = buildIdentifierKeys(objectIdentifiers);
182         return works.stream().filter(work -> matches(work, objectKeys));
183     }
184 
185     private boolean matches(MCRWork work, Set<String> objectIdentifiers) {
186         Set<String> workIdentifiers = buildIdentifierKeys(work.getIdentifiers());
187         workIdentifiers.retainAll(objectIdentifiers);
188         return !workIdentifiers.isEmpty();
189     }
190 
191     private Set<String> buildIdentifierKeys(List<Element> modsIdentifiers) {
192         Set<String> objectKeys = new HashSet<>();
193         for (Element modsIdentifier : modsIdentifiers) {
194             objectKeys.add(buildIdentifierKey(modsIdentifier));
195         }
196         return objectKeys;
197     }
198 
199     private String buildIdentifierKey(Element modsIdentifier) {
200         return modsIdentifier.getAttributeValue("type") + ":" + modsIdentifier.getTextTrim();
201     }
202 
203     /** Returns true, if there is a work in the ORCID profile that's origin is the given MyCoRe object */
204     public boolean containsWork(MCRObjectID oid) {
205         return findWork(oid).isPresent();
206     }
207 
208     public boolean containsOwnWork(MCRObjectID oid) {
209         return findOwnWork(oid).isPresent();
210     }
211 
212     /** Returns all works in the ORCID profile that have been added by ths MyCoRe application */
213     public Stream<MCRWork> getWorksFromThisApplication() {
214         return works.stream().filter(work -> work.getSource().isThisApplication());
215     }
216 }