001/*
002 * Copyright 2015 DuraSpace, Inc.
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.fcrepo.migration;
017
018import org.fcrepo.migration.pidlist.PidListManager;
019import org.slf4j.Logger;
020import org.springframework.context.ConfigurableApplicationContext;
021import org.springframework.context.support.FileSystemXmlApplicationContext;
022import org.springframework.core.io.ClassPathResource;
023
024import javax.xml.stream.XMLStreamException;
025import java.io.BufferedReader;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InputStreamReader;
029import java.util.List;
030
031import static org.slf4j.LoggerFactory.getLogger;
032
033/**
034 * A class that represents a command-line program to migrate a fedora 3
035 * repository to fedora 4.
036 *
037 * There are two main configuration options: the source and the handler.
038 *
039 * The source is responsible for exposing objects from a fedora repository,
040 * while the handler is responsible for processing each one.
041 * @author mdurbin
042 */
043public class Migrator {
044
045    private static final Logger LOGGER = getLogger(Migrator.class);
046
047    /**
048     * the main method.
049     * @param args the arguments
050     * @throws IOException IO exception
051     * @throws XMLStreamException xml stream exception
052     */
053    public static void main(final String [] args) throws IOException, XMLStreamException {
054        // Single arg with path to properties file is required
055        if (args.length != 1) {
056            printHelp();
057            return;
058        }
059
060        final ConfigurableApplicationContext context = new FileSystemXmlApplicationContext(args[0]);
061        final Migrator m = context.getBean("migrator", Migrator.class);
062        try {
063            m.run();
064        } finally {
065            context.close();
066        }
067    }
068
069    private ObjectSource source;
070
071    private StreamingFedoraObjectHandler handler;
072
073    private int limit;
074
075    private List<PidListManager> pidListManagers;
076
077    private boolean continueOnError;
078
079    /**
080     * the migrator. set limit to -1.
081     */
082    public Migrator() {
083        limit = -1;
084    }
085
086    /**
087     * set the limit.
088     * @param limit the limit
089     */
090    public void setLimit(final int limit) {
091        this.limit = limit;
092    }
093
094    /**
095     * set the source.
096     * @param source the object source
097     */
098    public void setSource(final ObjectSource source) {
099        this.source = source;
100    }
101
102
103    /**
104     * set the handler.
105     * @param handler the handler
106     */
107    public void setHandler(final StreamingFedoraObjectHandler handler) {
108        this.handler = handler;
109    }
110
111    /**
112     * set the list of PidListManagers
113     *
114     * @param pidListManagers the list
115     */
116    public void setPidListManagers(final List<PidListManager> pidListManagers) {
117        this.pidListManagers = pidListManagers;
118    }
119
120    /**
121     * set the continue on error flag
122     *
123     * @param flag flag indicating whether or not to continue on error.
124     */
125    public void setContinueOnError(final boolean flag) {
126        this.continueOnError = flag;
127    }
128
129    /**
130     * The constructor for migrator.
131     * @param source the source
132     * @param handler the handler
133     */
134    public Migrator(final ObjectSource source, final StreamingFedoraObjectHandler handler) {
135        this();
136        this.source = source;
137        this.handler = handler;
138    }
139
140    /**
141     * the run method for migrator.
142     *
143     * @throws XMLStreamException xml stream exception
144     */
145    public void run() throws XMLStreamException {
146        int index = 0;
147
148        for (final var iterator = source.iterator(); iterator.hasNext();) {
149            try (final var o = iterator.next()) {
150                final String pid = o.getObjectInfo().getPid();
151                if (pid != null) {
152                    // Process if limit is '-1', or we have not hit the non-negative 'limit'...
153                    if (!(limit < 0 || index++ < limit)) {
154                        LOGGER.info("Reached processing limit {}", limit);
155                        break;
156                    }
157
158                    if (acceptPid(pid)) {
159                        LOGGER.info("Processing \"" + pid + "\"...");
160                        try {
161                            o.processObject(handler);
162                        } catch (Exception ex) {
163                            final var message = String.format("MIGRATION_FAILURE: pid=\"%s\", message=\"%s\"",
164                                    pid, ex.getMessage());
165
166                            if (this.continueOnError) {
167                                LOGGER.error(message, ex);
168                            } else {
169                                throw new RuntimeException(message, ex);
170                            }
171                        }
172                    }
173                }
174            } catch (Exception ex) {
175                final var message = String.format("MIGRATION_FAILURE: UNREADABLE_OBJECT: message=\"%s\"",
176                        ex.getMessage());
177
178                if (this.continueOnError) {
179                    LOGGER.error(message, ex);
180                } else {
181                    throw new RuntimeException(message, ex);
182                }
183            }
184        }
185    }
186
187    private boolean acceptPid(final String pid) {
188
189        // If there is not manager, accept the PID
190        if (pidListManagers == null) {
191            return true;
192        }
193
194        // If any manager DOES NOT accept the PID, return false
195        for (PidListManager m : pidListManagers) {
196            if (!m.accept(pid)) {
197                return false;
198            }
199        }
200        return true;
201    }
202
203    private static void printHelp() throws IOException {
204        final StringBuilder sb = new StringBuilder();
205        sb.append("============================\n");
206        sb.append("Please provide the directory path to a configuration file!");
207        sb.append("\n");
208        sb.append("See: https://github.com/fcrepo-exts/migration-utils/blob/master/");
209        sb.append("src/main/resources/spring/migration-bean.xml");
210        sb.append("\n\n");
211        sb.append("The configuration file should contain the following (with appropriate values):");
212        sb.append("\n");
213        sb.append("~~~~~~~~~~~~~~\n");
214
215        final ClassPathResource resource = new ClassPathResource("spring/migration-bean.xml");
216        try (final InputStream example = resource.getInputStream();
217             final BufferedReader reader = new BufferedReader(new InputStreamReader(example))) {
218            String line = reader.readLine();
219            while (null != line) {
220                sb.append(line);
221                sb.append("\n");
222                line = reader.readLine();
223            }
224
225            sb.append("~~~~~~~~~~~~~~\n\n");
226            sb.append("See top of this output for details.\n");
227            sb.append("============================\n");
228            System.out.println(sb.toString());
229        }
230    }
231}