1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.meta;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.io.Writer;
25 import java.security.AccessController;
26 import java.security.PrivilegedActionException;
27 import java.sql.SQLException;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.Map;
34 import java.util.Set;
35
36 import org.apache.openjpa.conf.OpenJPAConfiguration;
37 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
38 import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
39 import org.apache.openjpa.jdbc.kernel.JDBCSeq;
40 import org.apache.openjpa.jdbc.schema.Column;
41 import org.apache.openjpa.jdbc.schema.DynamicSchemaFactory;
42 import org.apache.openjpa.jdbc.schema.LazySchemaFactory;
43 import org.apache.openjpa.jdbc.schema.Schema;
44 import org.apache.openjpa.jdbc.schema.SchemaGenerator;
45 import org.apache.openjpa.jdbc.schema.SchemaGroup;
46 import org.apache.openjpa.jdbc.schema.SchemaSerializer;
47 import org.apache.openjpa.jdbc.schema.SchemaTool;
48 import org.apache.openjpa.jdbc.schema.Table;
49 import org.apache.openjpa.jdbc.schema.XMLSchemaSerializer;
50 import org.apache.openjpa.jdbc.sql.DBDictionary;
51 import org.apache.openjpa.kernel.Seq;
52 import org.apache.openjpa.lib.conf.Configurations;
53 import org.apache.openjpa.lib.log.Log;
54 import org.apache.openjpa.lib.meta.ClassArgParser;
55 import org.apache.openjpa.lib.util.Files;
56 import org.apache.openjpa.lib.util.J2DoPrivHelper;
57 import org.apache.openjpa.lib.util.Localizer;
58 import org.apache.openjpa.lib.util.Options;
59 import org.apache.openjpa.lib.util.Services;
60 import org.apache.openjpa.meta.ClassMetaData;
61 import org.apache.openjpa.meta.FieldMetaData;
62 import org.apache.openjpa.meta.JavaTypes;
63 import org.apache.openjpa.meta.MetaDataFactory;
64 import org.apache.openjpa.meta.MetaDataModes;
65 import org.apache.openjpa.meta.QueryMetaData;
66 import org.apache.openjpa.meta.SequenceMetaData;
67 import org.apache.openjpa.meta.ValueStrategies;
68 import org.apache.openjpa.util.GeneralException;
69 import org.apache.openjpa.util.InternalException;
70 import org.apache.openjpa.util.MetaDataException;
71
72 /**
73 * Tool for manipulating class mappings and associated schema.
74 *
75 * @author Abe White
76 */
77 public class MappingTool
78 implements MetaDataModes {
79
80 public static final String SCHEMA_ACTION_NONE = "none";
81
82 public static final String ACTION_ADD = "add";
83 public static final String ACTION_REFRESH = "refresh";
84 public static final String ACTION_BUILD_SCHEMA = "buildSchema";
85 public static final String ACTION_DROP = "drop";
86 public static final String ACTION_VALIDATE = "validate";
87 public static final String ACTION_EXPORT = "export";
88 public static final String ACTION_IMPORT = "import";
89
90 public static final String[] ACTIONS = new String[]{
91 ACTION_ADD,
92 ACTION_REFRESH,
93 ACTION_BUILD_SCHEMA,
94 ACTION_DROP,
95 ACTION_VALIDATE,
96 ACTION_EXPORT,
97 ACTION_IMPORT,
98 };
99
100 private static final Localizer _loc =
101 Localizer.forPackage(MappingTool.class);
102
103 private final JDBCConfiguration _conf;
104 private final Log _log;
105 private final String _action;
106 private final boolean _meta;
107 private final int _mode;
108 private final DBDictionary _dict;
109
110 private MappingRepository _repos = null;
111 private SchemaGroup _schema = null;
112 private SchemaTool _schemaTool = null;
113 private String _schemaActions = SchemaTool.ACTION_ADD;
114 private boolean _readSchema = false;
115 private boolean _pks = false;
116 private boolean _fks = false;
117 private boolean _indexes = false;
118 private boolean _seqs = true;
119 private boolean _dropUnused = true;
120 private boolean _ignoreErrors = false;
121 private File _file = null;
122 private Writer _mappingWriter = null;
123 private Writer _schemaWriter = null;
124
125 // buffer metadatas to be dropped
126 private Set _dropCls = null;
127 private Set _dropMap = null;
128 private boolean _flush = false;
129 private boolean _flushSchema = false;
130
131 /**
132 * Constructor. Supply configuration and action.
133 */
134 public MappingTool(JDBCConfiguration conf, String action, boolean meta) {
135 _conf = conf;
136 _log = conf.getLog(JDBCConfiguration.LOG_METADATA);
137 _meta = meta;
138
139 if (action == null)
140 _action = ACTION_REFRESH;
141 else if (!Arrays.asList(ACTIONS).contains(action))
142 throw new IllegalArgumentException("action == " + action);
143 else
144 _action = action;
145
146 if (meta && ACTION_ADD.equals(_action))
147 _mode = MODE_META;
148 else if (meta && ACTION_DROP.equals(_action))
149 _mode = MODE_META | MODE_MAPPING | MODE_QUERY;
150 else
151 _mode = MODE_MAPPING;
152
153 _dict = _conf.getDBDictionaryInstance();
154 }
155
156 /**
157 * The action supplied on construction.
158 */
159 public String getAction() {
160 return _action;
161 }
162
163 /**
164 * Whether the action works on metadata as well as mappings.
165 */
166 public boolean isMetaDataAction() {
167 return _meta;
168 }
169
170 /**
171 * The schema modification policy, or <code>none</code>. See the
172 * ACTION constants in {@link SchemaTool}. May be a comma-separated
173 * list of values. Defaults to {@link SchemaTool#ACTION_ADD}.
174 */
175 public String getSchemaAction() {
176 return _schemaActions;
177 }
178
179 /**
180 * The schema modification policy, or <code>none</code>. See the
181 * ACTION constants in {@link SchemaTool}. May be a comma-separated
182 * list of values. Defaults to {@link SchemaTool#ACTION_ADD}.
183 */
184 public void setSchemaAction(String schemaAction) {
185 _schemaActions = schemaAction;
186 }
187
188 /**
189 * Set to true to read the entire schema before mapping.
190 * Leaving this option false saves time, but is dangerous when adding
191 * new mappings, because without full knowledge of the existing schema the
192 * mapping tool might create tables or indexes that conflict with
193 * existing components.
194 */
195 public boolean getReadSchema() {
196 return _readSchema;
197 }
198
199 /**
200 * Set to true to read the entire schema before mapping.
201 * Leaving this option false saves time, but is dangerous when adding
202 * new mappings, because without full knowledge of the existing schema the
203 * mapping tool might create tables or indexes that conflict with
204 * existing components.
205 */
206 public void setReadSchema(boolean readSchema) {
207 _readSchema = readSchema;
208 }
209
210 /**
211 * Whether to manipulate sequences. Defaults to true.
212 */
213 public boolean getSequences() {
214 return _seqs;
215 }
216
217 /**
218 * Whether to manipulate sequences. Defaults to true.
219 */
220 public void setSequences(boolean seqs) {
221 _seqs = seqs;
222 }
223
224 /**
225 * Whether indexes on existing tables should be manipulated.
226 * Defaults to false.
227 */
228 public boolean getIndexes() {
229 return _indexes;
230 }
231
232 /**
233 * Whether indexes on existing tables should be manipulated.
234 * Defaults to false.
235 */
236 public void setIndexes(boolean indexes) {
237 _indexes = indexes;
238 }
239
240 /**
241 * Whether foreign keys on existing tables should be manipulated.
242 * Defaults to false.
243 */
244 public boolean getForeignKeys() {
245 return _fks;
246 }
247
248 /**
249 * Whether foreign keys on existing tables should be manipulated.
250 * Defaults to false.
251 */
252 public void setForeignKeys(boolean fks) {
253 _fks = fks;
254 }
255
256 /**
257 * Whether primary keys on existing tables should be manipulated.
258 * Defaults to false.
259 */
260 public boolean getPrimaryKeys() {
261 return _pks;
262 }
263
264 /**
265 * Whether primary keys on existing tables should be manipulated.
266 * Defaults to false.
267 */
268 public void setPrimaryKeys(boolean pks) {
269 _pks = pks;
270 }
271
272 /**
273 * Whether schema components that are unused by any mapping will be
274 * dropped from this tool's {@link SchemaGroup}, and, depending on
275 * the schema action, from the database. Defaults to true.
276 */
277 public boolean getDropUnusedComponents() {
278 return _dropUnused;
279 }
280
281 /**
282 * Whether schema components that are unused by any mapping will be
283 * dropped from this tool's {@link SchemaGroup}, and, depending on
284 * the schema action, from the database. Defaults to true.
285 */
286 public void setDropUnusedComponents(boolean dropUnused) {
287 _dropUnused = dropUnused;
288 }
289
290 /**
291 * Whether and SQL errors should cause a failure or just issue a warning.
292 */
293 public void setIgnoreErrors(boolean ignoreErrors) {
294 _ignoreErrors = ignoreErrors;
295 }
296
297 /**
298 * Whether and SQL errors should cause a failure or just issue a warning.
299 */
300 public boolean getIgnoreErrors() {
301 return _ignoreErrors;
302 }
303
304 /**
305 * Return the schema tool to use for schema modification.
306 */
307 private SchemaTool newSchemaTool(String action) {
308 if (SCHEMA_ACTION_NONE.equals(action))
309 action = null;
310 SchemaTool tool = new SchemaTool(_conf, action);
311 tool.setIgnoreErrors(getIgnoreErrors());
312 tool.setPrimaryKeys(getPrimaryKeys());
313 tool.setForeignKeys(getForeignKeys());
314 tool.setIndexes(getIndexes());
315 tool.setSequences(getSequences());
316 return tool;
317 }
318
319 /**
320 * Set the schema tool to use for schema modification.
321 */
322 public void setSchemaTool(SchemaTool tool) {
323 _schemaTool = tool;
324 }
325
326 /**
327 * The stream to export the planned schema to as an XML document.
328 * If non-null, then the database schema will not be altered.
329 */
330 public Writer getSchemaWriter() {
331 return _schemaWriter;
332 }
333
334 /**
335 * The stream to export the planned schema to as an XML document.
336 * If non-null, then the database schema will not be altered.
337 */
338 public void setSchemaWriter(Writer schemaWriter) {
339 _schemaWriter = schemaWriter;
340 }
341
342 /**
343 * The stream to export the planned mappings to as an XML document.
344 * If non-null, then the mapping repository will not be altered.
345 */
346 public Writer getMappingWriter() {
347 return _mappingWriter;
348 }
349
350 /**
351 * The stream to export the planned mappings to as an XML document.
352 * If non-null, then the mapping repository will not be altered.
353 */
354 public void setMappingWriter(Writer mappingWriter) {
355 _mappingWriter = mappingWriter;
356 }
357
358 /**
359 * If adding metadata, the metadata file to add to.
360 */
361 public File getMetaDataFile() {
362 return _file;
363 }
364
365 /**
366 * If adding metadata, the metadata file to add to.
367 */
368 public void setMetaDataFile(File file) {
369 _file = file;
370 }
371
372 /**
373 * Return the repository to use to access mapping information.
374 * Defaults to a new {@link MappingRepository}.
375 */
376 public MappingRepository getRepository() {
377 if (_repos == null) {
378 _repos = _conf.newMappingRepositoryInstance();
379 _repos.setSchemaGroup(getSchemaGroup());
380 _repos.setValidate(_repos.VALIDATE_UNENHANCED, false);
381 }
382 return _repos;
383 }
384
385 /**
386 * Set the repository to use to access mapping information.
387 */
388 public void setRepository(MappingRepository repos) {
389 _repos = repos;
390 }
391
392 /**
393 * Return the schema group to use in mapping. If none has been set, the
394 * schema will be generated from the database.
395 */
396 public SchemaGroup getSchemaGroup() {
397 if (_schema == null) {
398 if (_action.indexOf(ACTION_BUILD_SCHEMA) != -1) {
399 DynamicSchemaFactory factory = new DynamicSchemaFactory();
400 factory.setConfiguration(_conf);
401 _schema = factory;
402 } else if (_readSchema
403 || contains(_schemaActions,SchemaTool.ACTION_RETAIN)
404 || contains(_schemaActions,SchemaTool.ACTION_REFRESH)) {
405 _schema = (SchemaGroup) newSchemaTool(null).getDBSchemaGroup().
406 clone();
407 } else {
408 // with this we'll just read tables as different mappings
409 // look for them
410 LazySchemaFactory factory = new LazySchemaFactory();
411 factory.setConfiguration(_conf);
412 factory.setPrimaryKeys(getPrimaryKeys());
413 factory.setForeignKeys(getForeignKeys());
414 factory.setIndexes(getIndexes());
415 _schema = factory;
416 }
417
418 if (_schema.getSchemas().length == 0)
419 _schema.addSchema();
420 }
421 return _schema;
422 }
423
424 /**
425 * Set the schema to use in mapping.
426 */
427 public void setSchemaGroup(SchemaGroup schema) {
428 _schema = schema;
429 }
430
431 /**
432 * Reset the internal repository. This is called automatically after
433 * every {@link #record}.
434 */
435 public void clear() {
436 _repos = null;
437 _schema = null;
438 _schemaTool = null;
439 _flush = false;
440 _flushSchema = false;
441 if (_dropCls != null)
442 _dropCls.clear();
443 if (_dropMap != null)
444 _dropMap.clear();
445 }
446
447 /**
448 * Records the changes that have been made to both the mappings and the
449 * associated schema, and clears the tool for further use. This also
450 * involves clearing the internal mapping repository.
451 */
452 public void record() {
453 record(null);
454 }
455
456 private void record(MappingTool.Flags flags) {
457 MappingRepository repos = getRepository();
458 MetaDataFactory io = repos.getMetaDataFactory();
459 ClassMapping[] mappings;
460 if (!ACTION_DROP.equals(_action))
461 mappings = repos.getMappings();
462 else if (_dropMap != null)
463 mappings = (ClassMapping[]) _dropMap.toArray
464 (new ClassMapping[_dropMap.size()]);
465 else
466 mappings = new ClassMapping[0];
467
468 try {
469 if (_dropCls != null && !_dropCls.isEmpty()) {
470 Class[] cls = (Class[]) _dropCls.toArray
471 (new Class[_dropCls.size()]);
472 if (!io.drop(cls, _mode, null))
473 _log.warn(_loc.get("bad-drop", _dropCls));
474 }
475
476 if (_flushSchema) {
477 // drop portions of the known schema that no mapping uses, and
478 // add sequences used for value generation
479 if (_dropUnused)
480 dropUnusedSchemaComponents(mappings);
481 addSequenceComponents(mappings);
482
483 // now run the schematool as long as we're doing some schema
484 // action and the user doesn't just want an xml output
485 String[] schemaActions = _schemaActions.split(",");
486 for (int i = 0; i < schemaActions.length; i++) {
487 if (!SCHEMA_ACTION_NONE.equals(schemaActions[i])
488 && (_schemaWriter == null || (_schemaTool != null
489 && _schemaTool.getWriter() != null))) {
490 SchemaTool tool = newSchemaTool(schemaActions[i]);
491
492 // configure the tool with additional settings
493 if (flags != null) {
494 tool.setDropTables(flags.dropTables);
495 tool.setDropSequences(flags.dropSequences);
496 tool.setWriter(flags.sqlWriter);
497 tool.setOpenJPATables(flags.openjpaTables);
498 }
499
500 tool.setSchemaGroup(getSchemaGroup());
501 tool.run();
502 tool.record();
503 }
504 }
505
506 // xml output of schema?
507 if (_schemaWriter != null) {
508 // serialize the planned schema to the stream
509 SchemaSerializer ser = new XMLSchemaSerializer(_conf);
510 ser.addAll(getSchemaGroup());
511 ser.serialize(_schemaWriter, ser.PRETTY);
512 _schemaWriter.flush();
513 }
514 }
515 if (!_flush)
516 return;
517
518 QueryMetaData[] queries = repos.getQueryMetaDatas();
519 SequenceMetaData[] seqs = repos.getSequenceMetaDatas();
520 Map output = null;
521
522 // if we're outputting to stream, set all metas to same file so
523 // they get placed in single string
524 if (_mappingWriter != null) {
525 output = new HashMap();
526 File tmp = new File("openjpatmp");
527 for (int i = 0; i < mappings.length; i++)
528 mappings[i].setSource(tmp, mappings[i].SRC_OTHER);
529 for (int i = 0; i < queries.length; i++)
530 queries[i].setSource(tmp, queries[i].getSourceScope(),
531 queries[i].SRC_OTHER);
532 for (int i = 0; i < seqs.length; i++)
533 seqs[i].setSource(tmp, seqs[i].getSourceScope(),
534 seqs[i].SRC_OTHER);
535 }
536
537 // store
538 if (!io.store(mappings, queries, seqs, _mode, output))
539 throw new MetaDataException(_loc.get("bad-store"));
540
541 // write to stream
542 if (_mappingWriter != null) {
543 PrintWriter out = new PrintWriter(_mappingWriter);
544 for (Iterator itr = output.values().iterator();
545 itr.hasNext();)
546 out.println((String) itr.next());
547 out.flush();
548 }
549 }
550 catch (RuntimeException re) {
551 throw re;
552 } catch (Exception e) {
553 throw new GeneralException(e);
554 } finally {
555 clear();
556 }
557 }
558
559 /**
560 * Drops schema components that appear to be unused from the local
561 * copy of the schema group.
562 */
563 private void dropUnusedSchemaComponents(ClassMapping[] mappings) {
564 FieldMapping[] fields;
565 for (int i = 0; i < mappings.length; i++) {
566 mappings[i].refSchemaComponents();
567 mappings[i].getDiscriminator().refSchemaComponents();
568 mappings[i].getVersion().refSchemaComponents();
569 fields = mappings[i].getDefinedFieldMappings();
570 for (int j = 0; j < fields.length; j++)
571 fields[j].refSchemaComponents();
572 }
573
574 // also allow the dbdictionary to ref any schema components that
575 // it adds apart from mappings
576 SchemaGroup group = getSchemaGroup();
577 Schema[] schemas = group.getSchemas();
578 Table[] tables;
579 for (int i = 0; i < schemas.length; i++) {
580 tables = schemas[i].getTables();
581 for (int j = 0; j < tables.length; j++)
582 _dict.refSchemaComponents(tables[j]);
583 }
584
585 group.removeUnusedComponents();
586 }
587
588 /**
589 * Add tables used by sequences to the given schema.
590 */
591 private void addSequenceComponents(ClassMapping[] mappings) {
592 SchemaGroup group = getSchemaGroup();
593 for (int i = 0; i < mappings.length; i++)
594 addSequenceComponents(mappings[i], group);
595 }
596
597 /**
598 * Add tables used by sequences to the given schema.
599 */
600 private void addSequenceComponents(ClassMapping mapping,
601 SchemaGroup group) {
602 SequenceMetaData smd = mapping.getIdentitySequenceMetaData();
603 Seq seq = null;
604 if (smd != null)
605 seq = smd.getInstance(null);
606 else if (mapping.getIdentityStrategy() == ValueStrategies.NATIVE
607 || (mapping.getIdentityStrategy() == ValueStrategies.NONE
608 && mapping.getIdentityType() == ClassMapping.ID_DATASTORE))
609 seq = _conf.getSequenceInstance();
610
611 if (seq instanceof JDBCSeq)
612 ((JDBCSeq) seq).addSchema(mapping, group);
613
614 FieldMapping[] fmds;
615 if (mapping.getEmbeddingMetaData() == null)
616 fmds = mapping.getDefinedFieldMappings();
617 else
618 fmds = mapping.getFieldMappings();
619 for (int i = 0; i < fmds.length; i++) {
620 smd = fmds[i].getValueSequenceMetaData();
621 if (smd != null) {
622 seq = smd.getInstance(null);
623 if (seq instanceof JDBCSeq)
624 ((JDBCSeq) seq).addSchema(mapping, group);
625 } else if (fmds[i].getEmbeddedMapping() != null)
626 addSequenceComponents(fmds[i].getEmbeddedMapping(), group);
627 }
628 }
629
630 ///////////
631 // Actions
632 ///////////
633
634 /**
635 * Run the configured action on the given instance.
636 */
637 public void run(Class cls) {
638 if (ACTION_ADD.equals(_action)) {
639 if (_meta)
640 addMeta(cls);
641 else
642 add(cls);
643 } else if (ACTION_REFRESH.equals(_action))
644 refresh(cls);
645 else if (ACTION_BUILD_SCHEMA.equals(_action))
646 buildSchema(cls);
647 else if (ACTION_DROP.equals(_action))
648 drop(cls);
649 else if (ACTION_VALIDATE.equals(_action))
650 validate(cls);
651 }
652
653 /**
654 * Add the mapping for the given instance.
655 */
656 private void add(Class cls) {
657 if (cls == null)
658 return;
659
660 MappingRepository repos = getRepository();
661 repos.setStrategyInstaller(new MappingStrategyInstaller(repos));
662 if (getMapping(repos, cls, true) != null) {
663 _flush = true;
664 _flushSchema = true;
665 }
666 }
667
668 /**
669 * Return the mapping for the given type, or null if the type is
670 * persistence-aware.
671 */
672 private static ClassMapping getMapping(MappingRepository repos, Class cls,
673 boolean validate) {
674 // this will parse all possible metadata rsrcs looking for cls, so
675 // will detect if p-aware
676 ClassMapping mapping = repos.getMapping(cls, null, false);
677 if (mapping != null)
678 return mapping;
679 if (!validate || cls.isInterface()
680 || repos.getPersistenceAware(cls) != null)
681 return null;
682 throw new MetaDataException(_loc.get("no-meta", cls));
683 }
684
685 /**
686 * Create a metadata for the given instance.
687 */
688 private void addMeta(Class cls) {
689 if (cls == null)
690 return;
691
692 _flush = true;
693 MappingRepository repos = getRepository();
694 repos.setResolve(MODE_MAPPING, false);
695 MetaDataFactory factory = repos.getMetaDataFactory();
696 factory.getDefaults().setIgnoreNonPersistent(false);
697 factory.setStoreMode(MetaDataFactory.STORE_VERBOSE);
698
699 ClassMetaData meta = repos.addMetaData(cls);
700 FieldMetaData[] fmds = meta.getDeclaredFields();
701 for (int i = 0; i < fmds.length; i++) {
702 if (fmds[i].getDeclaredTypeCode() == JavaTypes.OBJECT
703 && fmds[i].getDeclaredType() != Object.class)
704 fmds[i].setDeclaredTypeCode(JavaTypes.PC);
705 }
706 meta.setSource(_file, meta.getSourceType());
707 meta.setResolve(MODE_META, true);
708 }
709
710 /**
711 * Refresh or add the mapping for the given instance.
712 */
713 private void refresh(Class cls) {
714 if (cls == null)
715 return;
716
717 MappingRepository repos = getRepository();
718 repos.setStrategyInstaller(new RefreshStrategyInstaller(repos));
719 if (getMapping(repos, cls, true) != null) {
720 _flush = true;
721 _flushSchema = true;
722 }
723 }
724
725 /**
726 * Validate the mappings for the given class and its fields.
727 */
728 private void validate(Class cls) {
729 if (cls == null)
730 return;
731
732 MappingRepository repos = getRepository();
733 repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos));
734 if (getMapping(repos, cls, true) != null)
735 _flushSchema = !contains(_schemaActions,SCHEMA_ACTION_NONE)
736 && !contains(_schemaActions,SchemaTool.ACTION_ADD);
737 }
738
739 /**
740 * Create the schema using the mapping for the given instance.
741 */
742 private void buildSchema(Class cls) {
743 if (cls == null)
744 return;
745
746 MappingRepository repos = getRepository();
747 repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos));
748 if (getMapping(repos, cls, true) == null)
749 return;
750
751 // set any logical pks to non-logical so they get flushed
752 _flushSchema = true;
753 Schema[] schemas = _schema.getSchemas();
754 Table[] tables;
755 Column[] cols;
756 for (int i = 0; i < schemas.length; i++) {
757 tables = schemas[i].getTables();
758 for (int j = 0; j < tables.length; j++) {
759 if (tables[j].getPrimaryKey() == null)
760 continue;
761
762 tables[j].getPrimaryKey().setLogical(false);
763 cols = tables[j].getPrimaryKey().getColumns();
764 for (int k = 0; k < cols.length; k++)
765 cols[k].setNotNull(true);
766 }
767 }
768 }
769
770 /**
771 * Drop mapping for given class.
772 */
773 private void drop(Class cls) {
774 if (cls == null)
775 return;
776
777 if (_dropCls == null)
778 _dropCls = new HashSet();
779 _dropCls.add(cls);
780 if (!contains(_schemaActions,SchemaTool.ACTION_DROP))
781 return;
782
783 MappingRepository repos = getRepository();
784 repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos));
785 ClassMapping mapping = null;
786 try {
787 mapping = repos.getMapping(cls, null, false);
788 } catch (Exception e) {
789 }
790
791 if (mapping != null) {
792 _flushSchema = true;
793 if (_dropMap == null)
794 _dropMap = new HashSet();
795 _dropMap.add(mapping);
796 } else
797 _log.warn(_loc.get("no-drop-meta", cls));
798 }
799
800 ////////
801 // Main
802 ////////
803
804 /**
805 * Usage: java org.apache.openjpa.jdbc.meta.MappingTool [option]*
806 * [-action/-a <refresh | add | buildSchema | drop | validate | import
807 * | export>] <class name | .java file | .class file | .jdo file>*
808 * Where the following options are recognized.
809 * <ul>
810 * <li><i>-properties/-p <properties file or resource></i>: The
811 * path or resource name of a OpenJPA properties file containing
812 * information as outlined in {@link OpenJPAConfiguration}. Optional.</li>
813 * <li><i>-<property name> <property value></i>: All bean
814 * properties of the OpenJPA {@link JDBCConfiguration} can be set by
815 * using their names and supplying a value. For example:
816 * <code>-licenseKey adslfja83r3lkadf</code></li>
817 * <li><i>-file/-f <stdout | output file or resource></i>: Use
818 * this option to write the planned mappings to an XML document rather
819 * than store them in the repository. This option also specifies the
820 * metadata file to write to if using the <code>add</code> action with
821 * the <code>-meta true</code> flag, or the file to dump to if using
822 * the <code>export</code> action.</li>
823 * <li><i>-schemaAction/-sa <schema action | none></i>: The
824 * {@link SchemaTool} defines the actions possible. The actions will
825 * apply to all schema components used by the mappings involved.
826 * Unless you are running the mapping tool on all of your persistent
827 * types at once, be careful running schema actions that can drop data.
828 * It is possible to accidentally drop schema components that are
829 * used by classes you aren't currently running the tool over. The
830 * action defaults to <code>add</code>.</li>
831 * <li><i>-schemaFile/-sf <stdout | output file or resource></i>: Use
832 * this option to write the planned schema to an XML document rather
833 * than modify the data store.</li>
834 * <li><i>-sqlFile/-sql <stdout | output file or resource></i>: Use
835 * this option to write the planned schema changes as a SQL
836 * script rather than modifying the data store.</li>
837 * <li><i>-dropTables/-dt <true/t | false/f></i>: Corresponds to the
838 * same-named option in the {@link SchemaTool}.</li>
839 * <li><i>-dropSequences/-dsq <true/t | false/f></i>: Corresponds
840 * to the same-named option in the {@link SchemaTool}.</li>
841 * <li><i>-openjpaTables/-kt <true/t | false/f></i>: Corresponds to
842 * the same-named option in the {@link SchemaTool}.</li>
843 * <li><i>-ignoreErrors/-i <true/t | false/f></i>: Corresponds to the
844 * same-named option in the {@link SchemaTool}.</li>
845 * <li><i>-readSchema/-rs <true/t | false/f></i>: Set this to true
846 * to read the entire existing schema (even when false the parts of
847 * the schema used by classes the tool is run on will still be read).
848 * Turning on schema reading can ensure that no naming conflicts will
849 * occur, but it can take a long time.</li>
850 * <li><i>-primaryKeys/-pk <true/t | false/f></i>: Whether primary
851 * keys on existing tables are manipulated. Defaults to false.</li>
852 * <li><i>-foreignKeys/-fk <true/t | false/f></i>: Whether foreign
853 * keys on existing tables are manipulated. Defaults to false.</li>
854 * <li><i>-indexes/-ix <true/t | false/f></i>: Whether indexes on
855 * existing tables are manipulated. Defaults to false.</li>
856 * <li><i>-sequences/-sq <true/t | false/f></i>: Whether sequences
857 * are manipulated. Defaults to true.</li>
858 * <li><i>-schemas/-s <schema and table names></i>: A list of schemas
859 * and/or tables to read. Corresponds to the
860 * same-named option in the {@link SchemaGenerator}. This option
861 * is ignored if <code>readSchema</code> is false.</li>
862 * <li><i>-meta/-m <true/t | false/f></i>: Whether the given action
863 * applies to metadata as well as mappings.</li>
864 * </ul>
865 * The various actions are as follows.
866 * <ul>
867 * <li><i>refresh</i>: Bring the mapping information up-to-date
868 * with the class definitions. OpenJPA will attempt to use any provided
869 * mapping information, and fill in missing information. If the
870 * provided information conflicts with the class definition, the
871 * conflicting information will be discarded and the class/field will
872 * be re-mapped to new columns/tables. This is the default action.</li>
873 * <li><i>add</i>: If used with the <code>-meta</code> option, adds new
874 * default metadata for the given class(es). Otherwise, brings the
875 * mapping information up-to-date with the class
876 * definitions. OpenJPA will attempt to use any provided mapping
877 * information, and fill in missing information. OpenJPA will fail if
878 * the provided information conflicts with the class definition.</li>
879 * <li><i>buildSchema</i>: Create the schema matching the existing
880 * mappings for the given class(es). Any invalid mapping information
881 * will cause an exception.</li>
882 * <li><i>drop</i>: Delete mappings for the given classes. If used with
883 * the <code>-meta</code> option, also deletes metadata.</li>
884 * <li><i>validate</i>: Validate the given mappings. The mapping
885 * repository and schema will not be affected.</li>
886 * <li><i>import</i>: Import mappings from an XML document and store
887 * them as the current system mappings.</li>
888 * <li><i>export</i>: Dump the current mappings for the given classes to
889 * an XML document specified by the <code>file</code> option.</li>
890 * If used with the <code>-meta</code> option, the metadata will be
891 * included in the export.
892 * </ul>
893 * Each class supplied as an argument must have valid metadata. If
894 * no class arguments are given, the tool runs on all metadata files in
895 * the CLASSPATH.
896 * Examples:
897 * <ul>
898 * <li>Refresh the mappings for given package, without dropping any
899 * schema components:<br />
900 * <code>java org.apache.openjpa.jdbc.meta.MappingTool
901 * mypackage.jdo</code></li>
902 * <li>Refresh the mappings for all persistent classes in the classpath,
903 * dropping any unused columns and even tables:<br />
904 * <code>java org.apache.openjpa.jdbc.meta.MappingTool -sa refresh
905 * -dt true</code></li>
906 * <li>Make sure the mappings you've created by hand match the object
907 * model and schema:<br />
908 * <code>java org.apache.openjpa.jbdc.meta.MappingTool
909 * -a validate Person.java</code></li>
910 * <li>Remove the recorded mapping for a given class:<br />
911 * <code>java org.apache.openjpa.jbdc.meta.MappingTool
912 * -a drop Person.java</code></li>
913 * <li>Record the current mappings in an XML file:<br />
914 * <code>java org.apache.openjpa.jdbc.meta.MappingTool
915 * -f mypackage.orm -a export mypackage.jdo</code></li>
916 * </ul>
917 */
918 public static void main(String[] arguments)
919 throws IOException, SQLException {
920 Options opts = new Options();
921 final String[] args = opts.setFromCmdLine(arguments);
922 boolean ret = Configurations.runAgainstAllAnchors(opts,
923 new Configurations.Runnable() {
924 public boolean run(Options opts) throws IOException, SQLException {
925 JDBCConfiguration conf = new JDBCConfigurationImpl();
926 try {
927 return MappingTool.run(conf, args, opts);
928 } finally {
929 conf.close();
930 }
931 }
932 });
933 if (!ret)
934 System.err.println(_loc.get("tool-usage"));
935 }
936
937 /**
938 * Run the tool. Returns false if invalid options are given.
939 *
940 * @see #main
941 */
942 public static boolean run(JDBCConfiguration conf, String[] args,
943 Options opts)
944 throws IOException, SQLException {
945 // flags
946 Flags flags = new Flags();
947 flags.action = opts.removeProperty("action", "a", flags.action);
948 flags.schemaAction = opts.removeProperty("schemaAction", "sa",
949 flags.schemaAction);
950 flags.dropTables = opts.removeBooleanProperty
951 ("dropTables", "dt", flags.dropTables);
952 flags.openjpaTables = opts.removeBooleanProperty
953 ("openjpaTables", "ot", flags.openjpaTables);
954 flags.dropSequences = opts.removeBooleanProperty
955 ("dropSequences", "dsq", flags.dropSequences);
956 flags.readSchema = opts.removeBooleanProperty
957 ("readSchema", "rs", flags.readSchema);
958 flags.primaryKeys = opts.removeBooleanProperty
959 ("primaryKeys", "pk", flags.primaryKeys);
960 flags.indexes = opts.removeBooleanProperty("indexes", "ix",
961 flags.indexes);
962 flags.foreignKeys = opts.removeBooleanProperty("foreignKeys", "fk",
963 flags.foreignKeys);
964 flags.sequences = opts.removeBooleanProperty("sequences", "sq",
965 flags.sequences);
966 flags.ignoreErrors = opts.removeBooleanProperty
967 ("ignoreErrors", "i", flags.ignoreErrors);
968 flags.meta = opts.removeBooleanProperty("meta", "m", flags.meta);
969 String fileName = opts.removeProperty("file", "f", null);
970 String schemaFileName = opts.removeProperty("schemaFile", "sf", null);
971 String sqlFileName = opts.removeProperty("sqlFile", "sql", null);
972 String schemas = opts.removeProperty("s");
973 if (schemas != null)
974 opts.setProperty("schemas", schemas);
975
976 Configurations.populateConfiguration(conf, opts);
977 ClassLoader loader = conf.getClassResolverInstance().
978 getClassLoader(MappingTool.class, null);
979 if (flags.meta && ACTION_ADD.equals(flags.action))
980 flags.metaDataFile = Files.getFile(fileName, loader);
981 else
982 flags.mappingWriter = Files.getWriter(fileName, loader);
983 flags.schemaWriter = Files.getWriter(schemaFileName, loader);
984 flags.sqlWriter = Files.getWriter(sqlFileName, loader);
985
986 return run(conf, args, flags, loader);
987 }
988
989 /**
990 * Run the tool. Return false if an invalid option was given.
991 */
992 public static boolean run(JDBCConfiguration conf, String[] args,
993 Flags flags, ClassLoader loader)
994 throws IOException, SQLException {
995 // default action based on whether the mapping defaults fills in
996 // missing info
997 if (flags.action == null) {
998 if (conf.getMappingDefaultsInstance().defaultMissingInfo())
999 flags.action = ACTION_BUILD_SCHEMA;
1000 else
1001 flags.action = ACTION_REFRESH;
1002 }
1003
1004 // collect the classes to act on
1005 Log log = conf.getLog(OpenJPAConfiguration.LOG_TOOL);
1006 Collection classes = null;
1007 if (args.length == 0) {
1008 if (ACTION_IMPORT.equals(flags.action))
1009 return false;
1010 log.info(_loc.get("running-all-classes"));
1011 classes = conf.getMappingRepositoryInstance().
1012 loadPersistentTypes(true, loader);
1013 } else {
1014 classes = new HashSet();
1015 ClassArgParser classParser = conf.getMetaDataRepositoryInstance().
1016 getMetaDataFactory().newClassArgParser();
1017 classParser.setClassLoader(loader);
1018 Class[] parsed;
1019 for (int i = 0; args != null && i < args.length; i++) {
1020 parsed = classParser.parseTypes(args[i]);
1021 classes.addAll(Arrays.asList(parsed));
1022 }
1023 }
1024
1025 Class[] act = (Class[]) classes.toArray(new Class[classes.size()]);
1026 if (ACTION_EXPORT.equals(flags.action)) {
1027 // run exports until the first export succeeds
1028 ImportExport[] instances = newImportExports();
1029 for (int i = 0; i < instances.length; i++) {
1030 if (instances[i].exportMappings(conf, act, flags.meta, log,
1031 flags.mappingWriter))
1032 return true;
1033 }
1034 return false;
1035 }
1036 if (ACTION_IMPORT.equals(flags.action)) {
1037 // run exports until the first export succeeds
1038 ImportExport[] instances = newImportExports();
1039 for (int i = 0; i < instances.length; i++) {
1040 if (instances[i].importMappings(conf, act, args, flags.meta,
1041 log, loader))
1042 return true;
1043 }
1044 return false;
1045 }
1046
1047 MappingTool tool;
1048 try {
1049 tool = new MappingTool(conf, flags.action, flags.meta);
1050 } catch (IllegalArgumentException iae) {
1051 return false;
1052 }
1053
1054 // setup the tool
1055 tool.setIgnoreErrors(flags.ignoreErrors);
1056 tool.setMetaDataFile(flags.metaDataFile);
1057 tool.setMappingWriter(flags.mappingWriter);
1058 tool.setSchemaAction(flags.schemaAction);
1059 tool.setSchemaWriter(flags.schemaWriter);
1060 tool.setReadSchema(flags.readSchema
1061 && !ACTION_VALIDATE.equals(flags.action));
1062 tool.setPrimaryKeys(flags.primaryKeys);
1063 tool.setForeignKeys(flags.foreignKeys);
1064 tool.setIndexes(flags.indexes);
1065 tool.setSequences(flags.sequences || flags.dropSequences);
1066
1067 // and run the action
1068 for (int i = 0; i < act.length; i++) {
1069 log.info(_loc.get("tool-running", act[i], flags.action));
1070 if (i == 0 && flags.readSchema)
1071 log.info(_loc.get("tool-time"));
1072 tool.run(act[i]);
1073 }
1074 log.info(_loc.get("tool-record"));
1075 tool.record(flags);
1076 return true;
1077 }
1078
1079 /**
1080 * Create an {@link ImportExport} instance.
1081 */
1082 private static ImportExport[] newImportExports() {
1083 try {
1084 Class[] types = Services.getImplementorClasses(ImportExport.class);
1085 ImportExport[] instances = new ImportExport[types.length];
1086 for (int i = 0; i < types.length; i++)
1087 instances[i] = (ImportExport) AccessController.doPrivileged(
1088 J2DoPrivHelper.newInstanceAction(types[i]));
1089 return instances;
1090 } catch (Throwable t) {
1091 if (t instanceof PrivilegedActionException)
1092 t = ((PrivilegedActionException) t).getException();
1093 throw new InternalException(_loc.get("importexport-instantiate"),t);
1094 }
1095 }
1096
1097 private static boolean contains(String list, String key) {
1098 return (list == null) ? false : list.indexOf(key) != -1;
1099 }
1100
1101 /**
1102 * Run flags.
1103 */
1104 public static class Flags {
1105
1106 public String action = null;
1107 public boolean meta = false;
1108 public String schemaAction = SchemaTool.ACTION_ADD;
1109 public File metaDataFile = null;
1110 public Writer mappingWriter = null;
1111 public Writer schemaWriter = null;
1112 public Writer sqlWriter = null;
1113 public boolean ignoreErrors = false;
1114 public boolean readSchema = false;
1115 public boolean dropTables = false;
1116 public boolean openjpaTables = false;
1117 public boolean dropSequences = false;
1118 public boolean sequences = true;
1119 public boolean primaryKeys = false;
1120 public boolean foreignKeys = false;
1121 public boolean indexes = false;
1122 }
1123
1124 /**
1125 * Helper used to import and export mapping data.
1126 */
1127 public static interface ImportExport {
1128
1129 /**
1130 * Import mappings for the given classes based on the given arguments.
1131 */
1132 public boolean importMappings(JDBCConfiguration conf, Class[] act,
1133 String[] args, boolean meta, Log log, ClassLoader loader)
1134 throws IOException;
1135
1136 /**
1137 * Export mappings for the given classes based on the given arguments.
1138 */
1139 public boolean exportMappings(JDBCConfiguration conf, Class[] act,
1140 boolean meta, Log log, Writer writer)
1141 throws IOException;
1142 }
1143 }